From bfdc27b5684eebbd1b432f6062ca81b89385a9cb Mon Sep 17 00:00:00 2001 From: Earl Hyatt Date: Sun, 24 Mar 2024 11:49:21 -0400 Subject: [PATCH v4] 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. --- doc/emacs/kmacro.texi | 162 ++++++++++++ etc/NEWS | 11 + lisp/kmacro.el | 558 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 731 insertions(+) diff --git a/doc/emacs/kmacro.texi b/doc/emacs/kmacro.texi index e30def34475..4a8d4d4f093 100644 --- a/doc/emacs/kmacro.texi +++ b/doc/emacs/kmacro.texi @@ -42,6 +42,8 @@ Keyboard Macros * 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 @@ Keyboard Macro Step-Edit 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 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 diff --git a/etc/NEWS b/etc/NEWS index eda84d588a8..0968f7cb7be 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1388,6 +1388,17 @@ When this is non-nil, the lines of key sequences are displayed with the most recent line first. This is can be useful when working with macros with many lines, such as from 'kmacro-edit-lossage'. +** Kmacro Menu Mode + ++++ +*** New mode 'kmacro-menu-mode' and new command 'list-keyboard-macros'. +The new command 'list-keyboard-macros' the 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 +'kmacro-menu-mode'. It allows rearranging the macros in the ring, +duplicating them, deleting them, and editing their counters, formats, +and keys. + ** Miscellaneous --- diff --git a/lisp/kmacro.el b/lisp/kmacro.el index 897ebf14330..a16f70105c1 100644 --- a/lisp/kmacro.el +++ b/lisp/kmacro.el @@ -1388,6 +1388,564 @@ kmacro-redisplay (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 + + " " #'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 -- 2.34.1