From 380bc64ab9a5a4acadcdee3dccde49a7d1002e69 Mon Sep 17 00:00:00 2001 From: Earl Hyatt Date: Sat, 19 Aug 2023 18:26:45 -0400 Subject: [PATCH] Make using Edmacro better for long sequences of keys. * lisp/edmacro.el (edmacro-set-macro-to-region-lines, edmacro-reverse-key-order): New command and user option to make working with longer lists of keys (such as 'kmacro-edit-lossage') easier. (edit-kbd-macro): Move regexps used to identify pars of buffer to internal variables. (edmacro--macro-lines-regexp): Allow noting whether the most recent keys are displayed first. (edmacro-mode-font-lock-keywords): Allow noting whether the most recent keys are displayed first. --- lisp/edmacro.el | 108 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 11 deletions(-) diff --git a/lisp/edmacro.el b/lisp/edmacro.el index 69d20d2bad3..a2662f9a635 100644 --- a/lisp/edmacro.el +++ b/lisp/edmacro.el @@ -73,9 +73,18 @@ edmacro-eight-bits :type 'boolean :group 'kmacro) +(defcustom edmacro-reverse-key-order nil + "Non-nil if `edit-kbd-macro' should show the most recent keys first. + +This is useful when dealing with long lists of key sequences, such as +from `kmacro-edit-lossage'." + :type 'boolean + :group 'kmacro) + (defvar-keymap edmacro-mode-map "C-c C-c" #'edmacro-finish-edit - "C-c C-q" #'edmacro-insert-key) + "C-c C-q" #'edmacro-insert-key + "C-c C-r" #'edmacro-set-macro-to-region-lines) (defface edmacro-label '((default :inherit bold) @@ -88,7 +97,10 @@ edmacro-label :group 'kmacro) (defvar edmacro-mode-font-lock-keywords - `((,(rx bol (group (or "Command" "Key" "Macro") ":")) 0 'edmacro-label) + `((,(rx bol (group (or "Command" "Key" + (seq "Macro" (zero-or-one " (most recent first)"))) + ":")) + 0 'edmacro-label) (,(rx bol (group ";; Keyboard Macro Editor. Press ") (group (*? nonl)) @@ -166,7 +178,13 @@ edit-kbd-macro (let* ((oldbuf (current-buffer)) (mmac (edmacro-fix-menu-commands mac)) (fmt (edmacro-format-keys mmac 1)) - (fmtv (edmacro-format-keys mmac (not prefix))) + (fmtv (let ((fmtv (edmacro-format-keys mmac (not prefix)))) + (if (not edmacro-reverse-key-order) + fmtv + (with-temp-buffer + (insert fmtv) + (reverse-region (point-min) (point-max)) + (buffer-string))))) (buf (get-buffer-create "*Edit Macro*"))) (message "Formatting keyboard macro...done") (switch-to-buffer buf) @@ -181,6 +199,9 @@ edit-kbd-macro (setq-local font-lock-defaults '(edmacro-mode-font-lock-keywords nil nil nil nil)) (setq font-lock-multiline nil) + ;; Make buffer-local so that the commands still work + ;; even if the default value changes. + (make-local-variable 'edmacro-reverse-key-order) (erase-buffer) (insert (substitute-command-keys (concat @@ -202,7 +223,11 @@ edit-kbd-macro (insert "Key: none\n"))) (when (and mac-counter mac-format) (insert (format "Counter: %d\nFormat: \"%s\"\n" mac-counter mac-format)))) - (insert "\nMacro:\n\n") + (insert "\nMacro" + (if edmacro-reverse-key-order + " (most recent first)" + "") + ":\n\n") (save-excursion (insert fmtv "\n")) (recenter '(4)) @@ -255,6 +280,33 @@ format-kbd-macro ;;; Commands for *Edit Macro* buffer. +(defvar edmacro--skip-line-regexp + "[ \t]*\\($\\|;;\\|REM[ \t\n]\\)" + "A regexp identifying lines that should be ignored.") + +(defvar edmacro--command-line-regexp + "Command:[ \t]*\\([^ \t\n]*\\)[ \t]*$" + "A regexp identifying the line containing the command name.") + +(defvar edmacro--key-line-regexp + "Key:\\(.*\\)$" + "A regexp identifying the line containing the bound key sequence.") + +(defvar edmacro--counter-line-regexp + "Counter:[ \t]*\\([^ \t\n]*\\)[ \t]*$" + "A regexp identifying the line containing the counter value.") + +(defvar edmacro--format-line-regexp + "Format:[ \t]*\"\\([^\n]*\\)\"[ \t]*$" + "A regexp identifying the line containing the counter format.") + +(defvar edmacro--macro-lines-regexp + (rx "Macro" + (zero-or-one " (most recent first)") + ":" + (zero-or-more (any " \t\n"))) + "A regexp identifying the lines that precede the macro's contents.") + (defun edmacro-finish-edit () (interactive nil edmacro-mode) (unless (eq major-mode 'edmacro-mode) @@ -266,9 +318,9 @@ edmacro-finish-edit (top (point-min))) (goto-char top) (let ((case-fold-search nil)) - (while (cond ((looking-at "[ \t]*\\($\\|;;\\|REM[ \t\n]\\)") + (while (cond ((looking-at edmacro--skip-line-regexp) t) - ((looking-at "Command:[ \t]*\\([^ \t\n]*\\)[ \t]*$") + ((looking-at edmacro--command-line-regexp) (when edmacro-store-hook (error "\"Command\" line not allowed in this context")) (let ((str (match-string 1))) @@ -283,7 +335,7 @@ edmacro-finish-edit cmd))) (keyboard-quit)))) t) - ((looking-at "Key:\\(.*\\)$") + ((looking-at edmacro--key-line-regexp) (when edmacro-store-hook (error "\"Key\" line not allowed in this context")) (let ((key (kbd (match-string 1)))) @@ -303,21 +355,21 @@ edmacro-finish-edit (edmacro-format-keys key 1)))) (keyboard-quit)))))) t) - ((looking-at "Counter:[ \t]*\\([^ \t\n]*\\)[ \t]*$") + ((looking-at edmacro--counter-line-regexp) (when edmacro-store-hook (error "\"Counter\" line not allowed in this context")) (let ((str (match-string 1))) (unless (equal str "") (setq mac-counter (string-to-number str)))) t) - ((looking-at "Format:[ \t]*\"\\([^\n]*\\)\"[ \t]*$") + ((looking-at edmacro--format-line-regexp) (when edmacro-store-hook (error "\"Format\" line not allowed in this context")) (let ((str (match-string 1))) (unless (equal str "") (setq mac-format str))) t) - ((looking-at "Macro:[ \t\n]*") + ((looking-at edmacro--macro-lines-regexp) (goto-char (match-end 0)) nil) ((eobp) nil) @@ -336,7 +388,13 @@ edmacro-finish-edit (when (buffer-name obuf) (set-buffer obuf)) (message "Compiling keyboard macro...") - (let ((mac (edmacro-parse-keys str))) + (let ((mac (edmacro-parse-keys (if edmacro-reverse-key-order + (with-temp-buffer + (insert str) + (reverse-region (point-min) + (point-max)) + (buffer-string)) + str)))) (message "Compiling keyboard macro...done") (if store-hook (funcall store-hook mac) @@ -372,6 +430,34 @@ edmacro-insert-key (insert (edmacro-format-keys key t) "\n") (insert (edmacro-format-keys key) " "))) +(defun edmacro-set-macro-to-region-lines (beg end) + "Set the Macro text to the lines of the region. + +If BEG is not at the beginning of a line, it is moved to the +beginning of the line. If END is at the beginning of a line, +that line is excluded. Otherwise, if END is not at the +end of a line, it is moved to the end of the line." + (interactive "r" edmacro-mode) + ;; Use `save-excursion' to restore region if there are any errors. + ;; If there are no errors, update macro contents, then go to the + ;; beginning of the macro contents. + (let ((final-position)) + (save-excursion + (goto-char beg) + (unless (bolp) (setq beg (pos-bol))) + (goto-char end) + (unless (or (bolp) (eolp)) (setq end (pos-eol))) + (let ((text (buffer-substring beg end))) + (goto-char (point-min)) + (if (not (let ((case-fold-search nil)) + (re-search-forward edmacro--macro-lines-regexp nil t))) + (user-error "\"Macro:\" line not found") + (delete-region (match-end 0) + (point-max)) + (insert text) + (setq final-position (match-beginning 0))))) + (goto-char final-position))) + (defun edmacro-mode () "\\Keyboard Macro Editing mode. Press \ \\[edmacro-finish-edit] to save and exit. -- 2.34.1