Add command 'list-keyboard-macros' that works like 'list-buffers'.

The command 'list-keyboard-macros' allows editing and re-arranging
macros using 'tabulated-list-mode'.  Existing keyboard macros can be
duplicated or deleted.  Macro counters and counter formats can take new
values read from the minibuffer.  Macro keys can be edited using
'edit-kbd-macro'.

* doc/emacs/kmacro.texi (Kmacro Menu): Document the new command
and the menu's commands.
* etc/NEWS (Kmacro Menu Mode): Mention the new mode and command.
* lisp/kmacro.el (kmacro-menu-mark, kmacro-menu-marked)
(kmacro-menu-flagged): Add faces for marks and flags.
* lisp/kmacro.el (kmacro-menu-mode-map, kmacro-menu-mode): Add mode
and map.
* lisp/kmacro.el (list-keyboard-macros, kmacro-menu): Add command.
* lisp/kmacro.el (kmacro-menu--deletion-flags, kmacro-menu--marks)
(kmacro-menu--id-kmacro, kmacro-menu--id-position, kmacro-menu--kmacros)
(kmacro-menu--refresh, kmacro-menu--map-ids, kmacro-menu--replace-all)
(kmacro-menu--replace-at, kmacro-menu--query-revert, kmacro-menu--assert-row)
(kmacro-menu--propertize-keys, kmacro-menu--do-region)
(kmacro-menu--marks-exist-p): Add utility functions of mode
and commands.
* lisp/kmacro.el (kmacro-menu-mark, kmacro-menu-flag-for-deletion)
(kmacro-menu-unmark, kmacro-menu-unmark-backward)
(kmacro-menu-unmark-all): Add commands for marks and flags.
* lisp/kmacro.el (kmacro-menu-do-flagged-delete, kmacro-menu-do-copy)
(kmacro-menu-do-delete): Add commands that modify the ring.
* lisp/kmacro.el (kmacro-menu-edit-position, kmacro-menu-transpose)
(kmacro-menu-edit-format, kmacro-menu-edit-counter)
(kmacro-menu-edit-keys, kmacro-menu-edit-column): Add commands that
modify a keyboard macro.
This commit is contained in:
Earl Hyatt 2024-03-24 11:49:21 -04:00 committed by Eli Zaretskii
parent b2842b25bf
commit 7add47337b
3 changed files with 730 additions and 0 deletions

View file

@ -42,6 +42,8 @@ intelligent or general. For such things, Lisp must be used.
* 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
@ -616,3 +618,163 @@ including the final @kbd{C-j}), and appends them at the end of the
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

View file

@ -1510,6 +1510,16 @@ macros with many lines, such as from 'kmacro-edit-lossage'.
The user option 'proced-auto-update-flag' can now be set to 2 additional
values, which control automatic updates of Proced buffers that are not
displayed in some window.
** Kmacro Menu Mode
+++
*** New mode 'kmacro-menu-mode' and new command 'list-keyboard-macros'.
The new command 'list-keyboard-macros' is the keyboard-macro version
of commands like 'list-buffers' and 'list-processes', creating a listing
of the currently existing keyboards macros using the new mode
'kmacro-menu-mode'. It allows rearranging the macros in the ring,
duplicating them, deleting them, and editing their counters, formats,
and keys.
** Miscellaneous

View file

@ -1388,6 +1388,564 @@ To customize possible responses, change the \"bindings\" in
(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