* Edit Keyboard Macro:: Editing keyboard macros.
* Keyboard Macro Step-Edit:: Interactively executing and editing a keyboard
macro.
+* Kmacro Menu:: An interface for listing and editing
+ keyboard macros and the keyboard macro ring.
@end menu
@node Basic Keyboard Macro
keyboard macro; it then terminates the step-editing and replaces the
original keyboard macro with the edited macro.
@end itemize
+
+@node Kmacro Menu
+@section Listing and Editing Keyboard Macros
+@cindex Kmacro Menu
+
+@cindex listing current keyboard macros
+@kindex M-x list-keyboard-macros @key{RET}
+@findex kmacro-menu
+@findex list-keyboard-macros
+ To display a list of existing keyboard macros, type @kbd{M-x
+list-keyboard-macros @key{RET}}. This pops up the @dfn{Kmacro Menu} in
+a buffer named @file{*Keyboard Macro List*}. Each line in the list
+shows one macro's position, counter value, counter format, that counter
+value using that format, and macro keys. Here is an example of a macro
+list:
+
+@smallexample
+Position Counter Format Formatted Keys
+0 8 %02d 08 N : SPC <F3> RET
+1 0 %d 0 l o n g SPC p h r a s e
+@end smallexample
+
+@noindent
+The macros are listed with the current macro at the top in position
+number zero and the older macros in the order in which they are found in
+the keyboard macro ring (@pxref{Keyboard Macro Ring}). Using the Kmacro
+Menu, you can change the order of the macros and change their counters,
+counter formats, and keys. The Kmacro Menu is a read-only buffer, and
+can be changed only through the special commands described in this
+section. After a command is run, the Kmacro Menu displays changes to
+reflect the new values of the macro properties and the macro ring. You
+can use the usual cursor motion commands in this buffer, as well as
+special motion commands for navigating the table. To view a list of the
+special commands, type @kbd{C-h m} or @kbd{?} (@code{describe-mode}) in
+the Kmacro Menu.
+
+ You can use the following commands to change a macro's properties:
+
+@table @kbd
+@item #
+@findex kmacro-menu-edit-position
+@kindex # @r{(Kmacro Menu)}
+Change the position of the macro on the current line
+(@pxref{Keyboard Macro Ring}).
+
+@item C-x C-t
+@findex kmacro-menu-transpose
+@kindex C-x C-t @r{(Kmacro Menu)}
+Move the macro on the current line to the line above, like in
+@code{transpose-lines}.
+
+@item c
+@findex kmacro-menu-edit-counter
+@kindex c @r{(Kmacro Menu)}
+Change the counter value of the macro on the current line
+(@pxref{Keyboard Macro Counter}).
+
+@item f
+@findex kmacro-menu-edit-format
+@kindex f @r{(Kmacro Menu)}
+Change the counter format of the macro on the current line.
+
+@item e
+@findex kmacro-menu-edit-keys
+@kindex e @r{(Kmacro Menu)}
+Change the keys of the macro on the current line using
+@code{edit-kbd-macro} (@pxref{Edit Keyboard Macro}).
+
+@item @key{RET}
+@findex kmacro-menu-edit-column
+@kindex @key{RET} @r{(Kmacro Menu)}
+Change the value in the current column of the macro on the current line
+using commands above.
+@end table
+
+ The following commands delete or duplicate macros in the list:
+
+@table @kbd
+@item d
+@findex kmacro-menu-flag-for-deletion
+@item d @r{(Kmacro Menu)}
+Flag the macro on the current line for deletion, then move point to the
+next line (@code{kmacro-menu-flag-for-deletion}). The deletion flag is
+indicated by the character @samp{D} at the start of line. The deletion
+occurs only when you type the @kbd{x} command (see below).
+
+ If the region is active, this command flags all of the macros in the
+region.
+
+@item x
+@findex kmacro-menu-do-flagged-delete
+@item x @r{(Kmacro Menu)}
+Delete the macros in the list that have been flagged for deletion
+(@code{kmacro-menu-do-flagged-delete}).
+
+@item m
+@findex kmacro-menu-mark
+@item m @r{(Kmacro Menu)}
+Mark the macro on the current line, then move point to the next line
+(@code{kmacro-menu-mark}). Marked macros are indicated by the character
+@samp{*} at the start of line. Marked macros can be operated on by the
+@kbd{C} and @kbd{D} commands (see below).
+
+ If the region is active, this command marks all of the macros in the
+region.
+
+@item C
+@findex kmacro-menu-do-copy
+@item C @r{(Kmacro Menu)}
+This command copies macros by duplicating them at their current
+positions in the list (@code{kmacro-menu-do-copy}). For example,
+running this command on the macro at position number zero will insert a
+copy of that macro into position number one and move the remaining
+macros down.
+
+ If the region is active, this command duplicates the macros in the
+region. Otherwise, if there are marked macros, this command duplicates
+the marked macros. If there is no region nor are there marked macros,
+this command duplicates the macro on the current line. In the first two
+cases, the command prompts for confirmation before duplication.
+
+@item D
+@findex kmacro-menu-do-delete
+@item D @r{(Kmacro Menu)}
+This command deletes macros, removing them from the ring
+(@code{kmacro-menu-do-delete}). For example, running this command on
+the macro at position number zero will delete the current macro and then
+make the first macro in the macro ring (previously at position number
+one) the new current macro, popping it from the ring.
+
+ If the region is active, this command deletes the macros in the
+region. Otherwise, if there are marked macros, this command deletes the
+marked macros. If there is no region nor are there marked macros, this
+command deletes the macro on the current line. In all cases, the
+command prompts for confirmation before deletion.
+
+ This command is an alternative to the @kbd{d} and @kbd{x} commands
+(see above).
+
+@item u
+@findex kmacro-menu-unmark
+@item u @r{(Kmacro Menu)}
+Unmark and unflag the macro on the current line, then move point down
+to the next line (@code{kmacro-menu-unmark}). If there is an active
+region, this command unmarks and unflags all of the macros in the
+region.
+
+@item @key{DEL}
+@findex kmacro-menu-unmark-backward
+@item @key{DEL} @r{(Kmacro Menu)}
+Like the @kbd{u} command (see above), but move point up to the previous
+line when there is no active region
+(@code{kmacro-menu-unmark-backward}).
+
+@item U
+@findex kmacro-menu-unmark-all
+@item U @r{(Kmacro Menu)}
+Unmark and unflag all macros in the list
+(@code{kmacro-menu-unmark-all}).
+@end table
(let ((executing-kbd-macro nil))
(redisplay))))
+;;; Mode and commands for working with the ring in a table
+
+(defface kmacro-menu-mark '((t (:inherit font-lock-constant-face)))
+ "Face used for the Keyboard Macro Menu marks."
+ :group 'kmacro
+ :version "30.1")
+
+(defface kmacro-menu-flagged '((t (:inherit error)))
+ "Face used for keyboard macros flagged for deletion."
+ :group 'kmacro
+ :version "30.1")
+
+(defface kmacro-menu-marked '((t (:inherit warning)))
+ "Face used for keyboard macros marked for duplication."
+ :group 'kmacro
+ :version "30.1")
+
+(defvar-keymap kmacro-menu-mode-map
+ :doc "Keymap for `kmacro-menu-mode'."
+ :parent tabulated-list-mode-map
+ "#" #'kmacro-menu-edit-position
+ "c" #'kmacro-menu-edit-counter
+ "e" #'kmacro-menu-edit-keys
+ "f" #'kmacro-menu-edit-format
+ "RET" #'kmacro-menu-edit-column
+
+ "C" #'kmacro-menu-do-copy
+ "D" #'kmacro-menu-do-delete
+ "m" #'kmacro-menu-mark
+
+ "d" #'kmacro-menu-flag-for-deletion
+ "x" #'kmacro-menu-do-flagged-delete
+
+ "u" #'kmacro-menu-unmark
+ "U" #'kmacro-menu-unmark-all
+ "DEL"#'kmacro-menu-unmark-backward
+
+ "<remap> <transpose-lines>" #'kmacro-menu-transpose)
+
+(define-derived-mode kmacro-menu-mode tabulated-list-mode
+ "Keyboard Macro Menu"
+ "Major mode for listing and editing keyboard macros."
+ (make-local-variable 'kmacro-menu--marks)
+ (make-local-variable 'kmacro-menu--deletion-flags)
+ (setq-local tabulated-list-format
+ [("Position" 8 nil)
+ ("Counter" 8 nil :right-align t :pad-right 2)
+ ("Format" 8 nil)
+ ("Formatted" 10 nil)
+ ("Keys" 1 nil)])
+ (setq-local tabulated-list-padding 2)
+ (add-hook 'tabulated-list-revert-hook #'kmacro-menu--refresh nil t)
+ (tabulated-list-init-header)
+ (unless (kmacro-ring-empty-p)
+ (kmacro-menu--refresh)
+ (tabulated-list-print)))
+
+;;;###autoload
+(defalias 'kmacro-menu #'list-keyboard-macros)
+;;;###autoload
+(defun list-keyboard-macros ()
+ "List the keyboard macros."
+ (interactive)
+ (let ((buf (get-buffer-create "*Keyboard Macro List*")))
+ (with-current-buffer buf
+ (kmacro-menu-mode))
+ (pop-to-buffer buf)))
+
+;;;; Utility functions and mode data
+
+(defvar kmacro-menu--deletion-flags nil
+ "Alist of entries flagged for deletion.")
+
+(defvar kmacro-menu--marks nil
+ "Alist of entries marked for copying and duplication.")
+
+(defun kmacro-menu--id-kmacro (entry-id)
+ "Return the keyboard macro that is part of the ENTRY-ID."
+ (car entry-id))
+
+(defun kmacro-menu--id-position (entry-id)
+ "Return the ordinal position that is part of the ENTRY-ID."
+ (cdr entry-id))
+
+(defun kmacro-menu--kmacros ()
+ "Return the list of the existing keyboard macros or nil, if none are defined."
+ (when last-kbd-macro
+ (cons (kmacro-ring-head)
+ kmacro-ring)))
+
+(defun kmacro-menu--refresh ()
+ "Reset the list of keyboard macros."
+ (setq-local tabulated-list-entries
+ (seq-map-indexed (lambda (km idx)
+ (let ((cnt (kmacro--counter km))
+ (fmt (kmacro--format km)))
+ `((,km . ,idx)
+ [,(format "%d" idx)
+ ,(format "%d" cnt)
+ ,fmt
+ ,(format fmt cnt)
+ ,(format-kbd-macro (kmacro--keys km))])))
+ (kmacro-menu--kmacros))
+ kmacro-menu--deletion-flags nil
+ kmacro-menu--marks nil)
+ (tabulated-list-clear-all-tags))
+
+(defun kmacro-menu--map-ids (function)
+ "Apply FUNCTION to the current table's entry IDs in order.
+
+Return a list of the output of FUNCTION."
+ (mapcar function
+ (mapcar #'car
+ (seq-sort-by #'cdar #'< tabulated-list-entries))))
+
+(defun kmacro-menu--replace-all (kmacros)
+ "Replace the existing keyboard macros with those in KMACROS.
+
+The first element in the list overwrites the values of `last-kbd-macro',
+`kmacro-counter', and `kmacro-counter-format'. The remaining elements
+become the value of `kmacro-ring'.
+
+KMACROS is a list of `kmacro' objects."
+ (if (null kmacros)
+ (setq last-kbd-macro nil
+ kmacro-counter-format kmacro-default-counter-format
+ kmacro-counter 0
+ kmacro-ring nil)
+ (if (not (seq-every-p #'kmacro-p kmacros))
+ (error "All elements must satisfy `kmacro-p'")
+ (kmacro-split-ring-element (car kmacros))
+ (setq kmacro-ring (cdr kmacros)))))
+
+(defun kmacro-menu--replace-at (kmacro n)
+ "Replace the keyboard macro at position N with KMACRO.
+
+This function replaces all of the existing keyboard macros via
+`kmacro-menu--replace-all'. Except for the macro at position N, which will
+be KMACRO, the replacement macros are the existing macros identified in
+the table."
+ (kmacro-menu--replace-all
+ (kmacro-menu--map-ids (lambda (id)
+ (if (= n (kmacro-menu--id-position id))
+ kmacro
+ (kmacro-menu--id-kmacro id))))))
+
+(defun kmacro-menu--query-revert ()
+ "If the table differs from the existing macros, ask whether to revert table."
+ (when (and (not (equal (kmacro-menu--kmacros)
+ (kmacro-menu--map-ids #'kmacro-menu--id-kmacro)))
+ (yes-or-no-p "Table does not match existing keyboard macros. Stop and revert table?"))
+ (tabulated-list-revert)
+ (signal 'quit nil)))
+
+(defun kmacro-menu--assert-row (&optional id)
+ "Signal an error if point is not on a table row.
+
+ID is the tabulated list id of the supposed entry at point."
+ (unless (or id (tabulated-list-get-id))
+ (user-error "Not on a table row")))
+
+(defun kmacro-menu--propertize-keys (face)
+ "Redisplay the macro keys on the current line with FACE."
+ (tabulated-list-set-col 4 (propertize (aref (tabulated-list-get-entry) 4)
+ 'face face)))
+
+(defun kmacro-menu--do-region (function)
+ "Run FUNCTION on macros in the region or on the current line at the line start.
+
+If there is an active region, for each line in the region, move to the
+beginning of the line and apply FUNCTION to the table entry ID of the
+line. If there is no region, apply FUNCTION only to the table entry ID
+of the current line.
+
+When there is no active region, advance to the beginning of the next
+line after applying FUNCTION."
+ (if (use-region-p)
+ (save-excursion
+ (let* ((reg-beg (region-beginning))
+ (reg-end (region-end))
+ (line-beg (progn
+ (goto-char reg-beg)
+ (pos-bol)))
+ (line-end (progn
+ (goto-char reg-end)
+ (if (bolp)
+ reg-end
+ (pos-bol 2)))))
+ (goto-char line-beg)
+ (let ((id))
+ (while (and (< (point) line-end)
+ (setq id (tabulated-list-get-id)))
+ (kmacro-menu--assert-row id)
+ (funcall function id)
+ (forward-line 1)))))
+ (let ((id (tabulated-list-get-id)))
+ (kmacro-menu--assert-row id)
+ (goto-char (pos-bol))
+ (funcall function id)
+ (forward-line 1))))
+
+(defun kmacro-menu--marks-exist-p ()
+ "Return non-nil if markers exist for any table entries."
+ (let ((tag (gensym)))
+ (catch tag
+ (kmacro-menu--map-ids (lambda (id)
+ (when (alist-get (kmacro-menu--id-position id)
+ kmacro-menu--marks)
+ (throw tag t))))
+ nil)))
+
+;;;; Commands for Marks and Flags
+
+(defun kmacro-menu-mark ()
+ "Mark macros in the region or on the current line.
+
+If there's an active region, mark macros in the region; otherwise mark
+the macro on the current line. If marking the current line, move point
+to the next line when done.
+
+Marked macros can be operated on by `kmacro-menu-do-copy' and
+`kmacro-menu-do-delete'."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (kmacro-menu--do-region
+ (lambda (id)
+ (setf (alist-get (kmacro-menu--id-position id)
+ kmacro-menu--marks)
+ t)
+ (kmacro-menu--propertize-keys 'kmacro-menu-marked)
+ (tabulated-list-put-tag #("*" 0 1 (face kmacro-menu-mark))))))
+
+(defun kmacro-menu-flag-for-deletion ()
+ "Flag macros in the region or on the current line.
+
+If there's an active region, flag macros in the region; otherwise flag
+the macro on the current line. If there is no active region, move point
+to the next line when done.
+
+Flagged macros can be deleted via `kmacro-menu-do-flagged-delete'."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (kmacro-menu--do-region
+ (lambda (id)
+ (setf (alist-get (kmacro-menu--id-position id)
+ kmacro-menu--deletion-flags)
+ t)
+ (kmacro-menu--propertize-keys 'kmacro-menu-flagged)
+ (tabulated-list-put-tag #("D" 0 1 (face kmacro-menu-mark))))))
+
+(defun kmacro-menu-unmark ()
+ "Unmark and unflag macros in the region or on the current line.
+
+If there's an active region, unmark and unflag macros in the region;
+otherwise unmark and unflag the macro on the current line. If there is
+no active region, move point to the next line when done."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (kmacro-menu--do-region
+ (lambda (id)
+ (let ((pos (kmacro-menu--id-position id)))
+ (setf (alist-get pos kmacro-menu--deletion-flags) nil
+ (alist-get pos kmacro-menu--marks) nil))
+ (kmacro-menu--propertize-keys 'default)
+ (tabulated-list-put-tag " "))))
+
+(defun kmacro-menu-unmark-backward ()
+ "Like `kmacro-menu-unmark', but move backwards instead of forwards."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (let ((go-back (not (use-region-p))))
+ (kmacro-menu-unmark)
+ (when go-back
+ (forward-line -2))))
+
+(defun kmacro-menu-unmark-all ()
+ "Unmark and unflag all listed keyboard macros."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (setq-local kmacro-menu--deletion-flags nil
+ kmacro-menu--marks nil)
+ (save-excursion
+ (goto-char (point-min))
+ (while (tabulated-list-get-id)
+ (kmacro-menu--propertize-keys 'default)
+ (forward-line 1))
+ (tabulated-list-clear-all-tags)))
+
+;;;; Commands that Modify the Ring
+
+(defun kmacro-menu-do-flagged-delete ()
+ "Delete keyboard macros flagged via `kmacro-menu-flag-for-deletion'."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (let ((res)
+ (num-deletes 0))
+ (kmacro-menu--map-ids (lambda (id)
+ (if (alist-get (kmacro-menu--id-position id)
+ kmacro-menu--deletion-flags)
+ (setq num-deletes (1+ num-deletes))
+ (push (kmacro-menu--id-kmacro id) res))))
+ (when (yes-or-no-p (if (= 1 num-deletes)
+ "Delete 1 flagged keyboard macro?"
+ (format "Delete %d flagged keyboard macros?"
+ num-deletes)))
+ (kmacro-menu--replace-all
+ (nreverse res))
+ (tabulated-list-revert))))
+
+(defun kmacro-menu-do-copy ()
+ "Duplicate macros in the region, those with markers, or the one at point.
+
+Macros are duplicated at their current position in the macro ring.
+
+If there's an active region, duplicate macros in the region; otherwise
+duplicate the marked macros or, if there are no marks, the macro on the
+current line."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (let* ((region-exists (use-region-p))
+ (mark-exists (kmacro-menu--marks-exist-p))
+ (id-alist (if (or region-exists
+ (not mark-exists))
+ (let ((region-alist))
+ (kmacro-menu--do-region
+ (lambda (id)
+ (push (cons (kmacro-menu--id-position id)
+ t)
+ region-alist)))
+ region-alist)
+ kmacro-menu--marks))
+ (num-duplicates 0))
+ (let ((res))
+ (kmacro-menu--map-ids (lambda (id)
+ (let ((pos (kmacro-menu--id-position id))
+ (km (kmacro-menu--id-kmacro id)))
+ (push km res)
+ (when (alist-get pos id-alist)
+ (push km res)
+ (setq num-duplicates (1+ num-duplicates))))))
+ ;; Confirm the action if we operated on marks or the region, but
+ ;; don't confirm if operating on a single line without a region.
+ (when (if (or mark-exists region-exists)
+ (yes-or-no-p (if (= 1 num-duplicates)
+ "Copy (duplicate) 1 keyboard macro?"
+ (format "Copy (duplicate) %d keyboard macros?"
+ num-duplicates)))
+ t)
+ (kmacro-menu--replace-all (nreverse res))
+ (tabulated-list-revert)))))
+
+(defun kmacro-menu-do-delete ()
+ "Delete macros in the region, those with markers, or the one at point.
+
+If there's an active region, delete macros in the region; otherwise
+delete the marked macros or, if there are no marks, the macro on the
+current line."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--query-revert)
+ (let ((num-deletes 0)
+ (id-alist (if (or (use-region-p)
+ (not (kmacro-menu--marks-exist-p)))
+ (let ((region-alist))
+ (kmacro-menu--do-region
+ (lambda (id)
+ (push (cons (kmacro-menu--id-position id)
+ t)
+ region-alist)))
+ region-alist)
+ kmacro-menu--marks)))
+ (let ((res))
+ (kmacro-menu--map-ids (lambda (id)
+ (if (alist-get (kmacro-menu--id-position id)
+ id-alist)
+ (setq num-deletes (1+ num-deletes))
+ (push (kmacro-menu--id-kmacro id) res))))
+ (when (yes-or-no-p (if (= 1 num-deletes)
+ "Delete 1 keyboard macro?"
+ (format "Delete %d keyboard macros?"
+ num-deletes)))
+ (kmacro-menu--replace-all (nreverse res))
+ (tabulated-list-revert)))))
+
+;;;; Commands that Modify a Keyboard Macro
+
+(defun kmacro-menu-edit-position ()
+ "Move the keyboard macro at point to a new position.
+
+See the Info node `(emacs) Keyboard Macro Ring' for more information."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (let ((id (tabulated-list-get-id)))
+ (kmacro-menu--assert-row id)
+ (kmacro-menu--query-revert)
+ (let* ((new-position (min (length tabulated-list-entries)
+ (max 0
+ (read-number "New position: " 0))))
+ (old-km (kmacro-menu--id-kmacro id))
+ (old-pos (kmacro-menu--id-position id)))
+ (unless (= old-pos new-position)
+ (kmacro-menu--replace-all
+ (let ((res)
+ (true-new-pos (if (> new-position old-pos)
+ (1+ new-position)
+ new-position)))
+ (kmacro-menu--map-ids (lambda (this-id)
+ (let ((this-km (kmacro-menu--id-kmacro this-id))
+ (this-pos (kmacro-menu--id-position this-id)))
+ (unless (= old-pos this-pos)
+ (when (= this-pos true-new-pos)
+ (push old-km res))
+ (push this-km res)))))
+ (when (>= true-new-pos
+ (length tabulated-list-entries))
+ (push old-km res))
+ (nreverse res)))
+ (tabulated-list-revert)))))
+
+(defun kmacro-menu-transpose ()
+ "Swap the keyboard macro at point with the one above, then move to the next line.
+
+If point is on the first line (position number 0), then swap the macros
+at position numbers 0 and 1, then move point to the third line.
+
+Note that this is the earlier position in the ring, not the sorted
+table."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (let ((id (tabulated-list-get-id)))
+ (kmacro-menu--assert-row id)
+ (kmacro-menu--query-revert)
+ (let* ((old-pos (kmacro-menu--id-position id))
+ (first-line (= 0 old-pos))
+ (end-lines-forward (if first-line
+ 2
+ (+ 3 old-pos))))
+ ;; When transposing the first two macros, we don't use
+ ;; `kmacro-swap-ring' here because it is possible for the user to
+ ;; choose to not refresh the table when it is out of date.
+ (kmacro-menu--replace-all
+ (let ((res))
+ (kmacro-menu--map-ids
+ (if first-line
+ (let ((old-km (kmacro-menu--id-kmacro id)))
+ (lambda (this-id)
+ (let ((this-pos (kmacro-menu--id-position this-id)))
+ (unless (= 0 this-pos)
+ (push (kmacro-menu--id-kmacro this-id) res)
+ (when (= 1 this-pos)
+ (push old-km res))))))
+ (let ((new-pos (1- old-pos)))
+ (lambda (this-id)
+ (let ((this-pos (kmacro-menu--id-position this-id)))
+ (unless (= old-pos this-pos)
+ (when (= new-pos this-pos)
+ (push (kmacro-menu--id-kmacro id) res))
+ (push (kmacro-menu--id-kmacro this-id) res)))))))
+ (nreverse res)))
+ (tabulated-list-revert)
+ (goto-char (point-min))
+ (forward-line end-lines-forward))))
+
+(defun kmacro-menu-edit-format ()
+ "Edit the counter format of the keyboard macro at point.
+
+Valid counter formats are those for integers accepted by the function
+`format'.
+
+See the command `kmacro-set-format' and the Info node `(emacs) Keyboard
+Macro Counter' for more information."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (let ((id (tabulated-list-get-id)))
+ (kmacro-menu--assert-row id)
+ (kmacro-menu--query-revert)
+ (let ((km (kmacro-menu--id-kmacro id)))
+ (kmacro-menu--replace-at
+ (kmacro (kmacro--keys km)
+ (kmacro--counter km)
+ (read-string "New format: " nil nil
+ (list kmacro-default-counter-format
+ (kmacro--format km))))
+ (kmacro-menu--id-position id))
+ (tabulated-list-revert))))
+
+(defun kmacro-menu-edit-counter ()
+ "Edit the counter of the keyboard macro at point.
+
+See Info node `(emacs) Keyboard Macro Counter' for more
+information."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (let ((id (tabulated-list-get-id)))
+ (kmacro-menu--assert-row id)
+ (kmacro-menu--query-revert)
+ (let ((km (kmacro-menu--id-kmacro id)))
+ (kmacro-menu--replace-at
+ (kmacro (kmacro--keys km)
+ (read-number "New counter: "
+ (list 0
+ (kmacro--counter
+ (kmacro-menu--id-kmacro id))))
+ (kmacro--format km))
+ (kmacro-menu--id-position id))
+ (tabulated-list-revert))))
+
+(defun kmacro-menu-edit-keys ()
+ "Edit the keys of the keyboard macro at point via `edmacro-mode'.
+
+See Info node `(emacs) Edit Keyboard Macro' for more
+information."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (let ((id (tabulated-list-get-id)))
+ (kmacro-menu--assert-row id)
+ (kmacro-menu--query-revert)
+ (let* ((old-km (kmacro-menu--id-kmacro id)))
+ (edit-kbd-macro (kmacro--keys old-km)
+ nil
+ nil
+ (lambda (mac)
+ (kmacro-menu--replace-at
+ (kmacro mac
+ (kmacro--counter old-km)
+ (kmacro--format old-km))
+ (kmacro-menu--id-position id))
+ (tabulated-list-revert))))))
+
+(defun kmacro-menu-edit-column ()
+ "Edit the value in the current column of the keyboard macro at point."
+ (declare (modes kmacro-menu-mode))
+ (interactive nil kmacro-menu-mode)
+ (kmacro-menu--assert-row)
+ (kmacro-menu--query-revert)
+ (pcase (get-text-property (point) 'tabulated-list-column-name)
+ ('nil (let ((pos (point)))
+ ;; If we didn't find a column, try moving forwards or
+ ;; backwards to the nearest column.
+ (tabulated-list-next-column 1)
+ (when (= pos (point))
+ (tabulated-list-previous-column 1))
+ (if (null (get-text-property (point) 'tabulated-list-column-name))
+ (user-error "No column at point")
+ (kmacro-menu-edit-column))))
+ ("Position" (call-interactively #'kmacro-menu-edit-position))
+ ("Counter" (call-interactively #'kmacro-menu-edit-counter))
+ ("Format" (call-interactively #'kmacro-menu-edit-format))
+ ("Formatted" (user-error "Formatted counter is not editable"))
+ ("Keys" (call-interactively #'kmacro-menu-edit-keys))))
+
(provide 'kmacro)
;;; kmacro.el ends here