[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
bug#61549: 30.0.50; [PATCH] New keyboard macro counter functions
From: |
Alex Bochannek |
Subject: |
bug#61549: 30.0.50; [PATCH] New keyboard macro counter functions |
Date: |
Sun, 05 Mar 2023 19:37:21 -0800 |
User-agent: |
Gnus/5.13 (Gnus v5.13) |
Eli,
Took me a little bit longer to find time to do this and I now have
incorporated your feedback in the below patch. Thank you for your
perspective on prefix, that made a lot of sense and I reworked that part
of the code to be consistent with how it usually works. I was not aware
that the interactive code `p' defaults to 1 in the absence of a prefix.
I couldn't find a place where this is documented and it simplified the
code.
I updated the docstrings as you suggested and even though checkdoc
complained about the lack of a period on the first line, I figured it's
better to keep below the line length limits.
Let me know if you would like to see any other changes, I always
appreciate constructive feedback!
I am attaching the changes to:
kmacro.texi
kmacro.el
kmacro-tests.el
The changelog as well as NEWS and emacs.texi remain the same from my
original message.
diff --git a/doc/emacs/kmacro.texi b/doc/emacs/kmacro.texi
index fc1402b489d..c3d74bb39ed 100644
--- a/doc/emacs/kmacro.texi
+++ b/doc/emacs/kmacro.texi
@@ -35,6 +35,7 @@ Keyboard Macros
* Basic Keyboard Macro:: Defining and running keyboard macros.
* Keyboard Macro Ring:: Where previous keyboard macros are saved.
* Keyboard Macro Counter:: Inserting incrementing numbers in macros.
+* Advanced Macro Counter:: Advanced macro counter commands.
* Keyboard Macro Query:: Making keyboard macros do different things each
time.
* Save Keyboard Macro:: Giving keyboard macros names; saving them in
@@ -364,6 +365,123 @@ Keyboard Macro Counter
keyboard macro counter. @xref{Number Registers}. For most purposes,
it is simpler to use a keyboard macro counter.
+@node Advanced Macro Counter
+@section Advanced Macro Counter Commands
+
+ The counter associated with a keyboard macro is sufficient in most
+cases. If additional counters are required for a macro, registers can
+be used and these advanced macro counter commands simplify the
+interaction between the two. Additional commands are provided to
+terminate a macro after a predefined number of runs.
+
+
+@table @kbd
+@item C-x C-k C-r l
+Load the value of a number register into the macro counter
+(@code{kmacro-reg-load-counter}).
+@item C-x C-k C-r s
+Save the value of the macro counter to a number register
+(@code{kmacro-reg-save-counter}).
+@end table
+
+@table @kbd
+@item C-x C-k C-r a =
+Compare if the macro counter is equal to the value of a register and
+increment the counter if it is (@code{kmacro-reg-add-counter-equal}).
+@item C-x C-k C-r a <
+Compare if the macro counter is less than the value of a register and
+increment the counter if it is (@code{kmacro-reg-add-counter-less}).
+@item C-x C-k C-r a >
+Compare if the macro counter is greater than the value of a register
+and increment the counter if it is
+(@code{kmacro-reg-add-counter-greater}).
+@end table
+
+@table @kbd
+@item C-x C-k C-q =
+Compare if the macro counter is equal to the prefix and terminate the
+macro if it is (@code{kmacro-quit-counter-equal}).
+@item C-x C-k C-q <
+Compare if the macro counter is less than the prefix and terminate the
+macro if it is (@code{kmacro-quit-counter-less}).
+@item C-x C-k C-q >
+Compare if the macro counter is greater than the prefix and terminate
+the macro if it is (@code{kmacro-quit-counter-greater}).
+@end table
+
+@findex kmacro-reg-load-counter
+@kindex C-x C-k C-r l
+@findex kmacro-reg-save-counter
+@kindex C-x C-k C-r s
+ The command @kbd{C-x C-k C-r l} (@code{kmacro-reg-load-counter})
+prompts for the register name from which to load a number into the
+macro counter. The command @kbd{C-x C-k C-r s}
+(@code{kmacro-reg-save-counter}) prompts for the register name into
+which to save the macro counter's value. Both @kbd{C-x C-k C-r l}
+(@code{kmacro-reg-load-counter}) and @kbd{C-x C-k C-r s}
+(@code{kmacro-reg-save-counter}) show a preview of the registers by
+default. @xref{Registers}. Both commands can be used during or
+outside a keyboard macro definition.
+
+@findex kmacro-reg-add-counter-equal
+@kindex C-x C-k C-r a =
+@findex kmacro-reg-add-counter-less
+@kindex C-x C-k C-r a <
+@findex kmacro-reg-add-counter-greater
+@kindex C-x C-k C-r a >
+ The @kbd{C-x C-k C-r a =} (@code{kmacro-reg-add-counter-equal}),
+@kbd{C-x C-k C-r a <} (@code{kmacro-reg-add-counter-less}), and
+@kbd{C-x C-k C-r a >} (@code{kmacro-reg-add-counter-greater}) commands
+all follow the same pattern. During keyboard macro definition, the
+command prompts for a register name (with preview by default), the
+contents of which will be compared with the macro counter's value. If
+the counter is equal to (@code{=}), less than (@code{<}), or greater
+than (@code{>}) the number register's contents, the counter will be
+incremented by the numeric prefix or one if no prefix was given to the
+command.
+
+ For example,
+
+@example
+C-u 2 C-x C-k C-r a > N
+@end example
+
+@noindent
+compares the counter with the contents of register @code{N} and if the
+counter is greater than that, increases it by two.
+
+@findex kmacro-quit-counter-equal
+@kindex C-x C-k C-q =
+@findex kmacro-quit-counter-less
+@kindex C-x C-k C-q <
+@findex kmacro-quit-counter-greater
+@kindex C-x C-k C-q >
+ Finally, the @kbd{C-x C-k C-q =} (@code{kmacro-quit-counter-equal}),
+@kbd{C-x C-k C-q <} (@code{kmacro-quit-counter-less}), and @kbd{C-x
+C-k C-q >} (@code{kmacro-quit-counter-greater}) commands compare the
+macro counter with the prefix given and terminate the execution of the
+macro, if the comparison succeeds. If no numeric prefix is given, the
+counter will be compared with zero. The macro is terminated using the
+@code{keyboard-quit} function. Using this command to exit from a
+macro that has been called by another macro is not supported, the
+entire executing macro is terminated.
+
+ The quit commands can be used to construct the equivalent of a
+@code{while}-loop. This example will stop after ten executions
+assuming the starting value for the macro counter is the default zero.
+
+@example
+C-u 10 C-x C-k C-q = C-x C-k C-i @key{RET}
+@end example
+
+ With the default counter value of zero, the macro called with a
+prefix of @code{C-u C-u} to execute sixteen times, will stop after ten
+iterations. The counter values that have been inserted will be from 0
+to 9. If the counter starts out a different value below ten, it will
+still stop at ten, because the counter does not actually count macro
+executions, but is incremented explicitly by the @code{C-x C-k C-i}
+command.
+
@node Keyboard Macro Query
@section Executing Macros with Variations
diff --git a/lisp/kmacro.el b/lisp/kmacro.el
index 94d8794bd23..d0630a1998f 100644
--- a/lisp/kmacro.el
+++ b/lisp/kmacro.el
@@ -183,10 +183,18 @@ kmacro-keymap
"C-l" #'kmacro-call-ring-2nd-repeat
;; macro counter
- "C-f" #'kmacro-set-format
- "C-c" #'kmacro-set-counter
- "C-i" #'kmacro-insert-counter
- "C-a" #'kmacro-add-counter
+ "C-f" #'kmacro-set-format
+ "C-c" #'kmacro-set-counter
+ "C-i" #'kmacro-insert-counter
+ "C-a" #'kmacro-add-counter
+ "C-r l" #'kmacro-reg-load-counter
+ "C-r s" #'kmacro-reg-save-counter
+ "C-r a =" #'kmacro-reg-add-counter-equal
+ "C-r a <" #'kmacro-reg-add-counter-less
+ "C-r a >" #'kmacro-reg-add-counter-greater
+ "C-q =" #'kmacro-quit-counter-equal
+ "C-q <" #'kmacro-quit-counter-less
+ "C-q >" #'kmacro-quit-counter-greater
;; macro editing
"C-e" #'kmacro-edit-macro-repeat
@@ -347,6 +355,96 @@ kmacro-add-counter
(kmacro-display-counter)))
+(defun kmacro-reg-load-counter (register)
+ "Load the value of REGISTER into `kmacro-counter'."
+ (interactive
+ (list (register-read-with-preview "Load register to counter: ")))
+ (let ((register-val (get-register register)))
+ (when (numberp register-val)
+ (setq kmacro-counter register-val))))
+
+
+(defun kmacro-reg-save-counter (register)
+ "Save the value of `kmacro-counter' to REGISTER."
+ (interactive
+ (list (register-read-with-preview "Save counter to register: ")))
+ (set-register register kmacro-counter))
+
+
+(defun kmacro-reg-add-counter-equal (&optional arg)
+ "Increment `kmacro-counter' by ARG if the counter is equal to a
+register's value.
+ARG is the numeric prefix argument that defaults to one."
+ (interactive "p")
+ (let
+ ((register (register-read-with-preview "Compare counter to register: ")))
+ (kmacro-reg-add-counter '= register arg)))
+
+
+(defun kmacro-reg-add-counter-less (&optional arg)
+ "Increment `kmacro-counter' by ARG if the counter is less than a
+register's value.
+ARG is the numeric prefix argument that defaults to one."
+ (interactive "p")
+ (let
+ ((register (register-read-with-preview "Compare counter to register: ")))
+ (kmacro-reg-add-counter '< register arg)))
+
+
+(defun kmacro-reg-add-counter-greater (&optional arg)
+ "Increment `kmacro-counter' by ARG if the counter is greater than
+a register's value.
+ARG is the numeric prefix argument that defaults to one."
+ (interactive "p")
+ (let
+ ((register (register-read-with-preview "Compare counter to register: ")))
+ (kmacro-reg-add-counter '> register arg)))
+
+
+(defun kmacro-reg-add-counter (pred register arg)
+ "Increment `kmacro-counter' by ARG if predicate PRED returns
+non-nil.
+PRED is called with two arguments: `kmacro-counter' and REGISTER."
+ (let ((register-val (get-register register)))
+ (when (apply pred (list kmacro-counter register-val))
+ (setq current-prefix-arg nil)
+ (kmacro-add-counter arg))))
+
+
+(defun kmacro-quit-counter-equal (&optional arg)
+ "Quit the keyboard macro if `kmacro-counter' is equal to ARG.
+ARG is the numeric prefix argument that defaults to zero."
+ (interactive "P")
+ (kmacro-quit-counter '= arg))
+
+
+(defun kmacro-quit-counter-less (&optional arg)
+ "Quit the keyboard macro if `kmacro-counter' is less than ARG.
+ARG is the numeric prefix argument that defaults to zero."
+ (interactive "P")
+ (kmacro-quit-counter '< arg))
+
+
+(defun kmacro-quit-counter-greater (&optional arg)
+ "Quit the keyboard macro if `kmacro-counter' is greater than ARG.
+ARG is the numeric prefix argument that defaults to zero."
+ (interactive "P")
+ (kmacro-quit-counter '> arg))
+
+
+(defun kmacro-quit-counter (pred &optional arg)
+ "Quit the keyboard macro if predicate PRED returns non-nil.
+PRED is called with two arguments: `kmacro-counter' and ARG."
+ (when kmacro-initial-counter-value
+ (setq kmacro-counter kmacro-initial-counter-value
+ kmacro-initial-counter-value nil))
+ (let ((arg
+ (cond ((null arg) 0)
+ (t (prefix-numeric-value arg)))))
+ (when (apply pred (list kmacro-counter arg))
+ (keyboard-quit))))
+
+
(defun kmacro-loop-setup-function ()
"Function called prior to each iteration of macro."
;; Restore macro counter format to initial format, so it is ok to change
diff --git a/test/lisp/kmacro-tests.el b/test/lisp/kmacro-tests.el
index 551fd8b60fc..42ada9f4f94 100644
--- a/test/lisp/kmacro-tests.el
+++ b/test/lisp/kmacro-tests.el
@@ -275,6 +275,205 @@ kmacro-tests-start-insert-counter-appends-to-macro
;; Verify that the recording state has changed.
(should (equal defining-kbd-macro 'append))))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-load ()
+ "`kmacro-reg-load-counter' loads the value of register into counter"
+ (set-register ?\C-r 4) ;; Should be safe as a register name
+ (kmacro-tests-simulate-command '(kmacro-set-counter 1))
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ "\C-x\C-k\C-i"
+ ;; Load from register
+ "\C-x\C-k\C-rl\C-r"
+ ))
+ (kmacro-tests-should-insert "1245"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 2)))
+ (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-save ()
+ "`kmacro-reg-save-counter' saves counter to register"
+ (set-register ?\C-r nil) ;; Should be safe as a register name
+ (kmacro-tests-simulate-command '(kmacro-set-counter 1))
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Save to register
+ "\C-x\C-k\C-rs\C-r"
+ ;; Add to counter
+ "\C-u2\C-x\C-k\C-a"
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Insert register
+ "\C-xri\C-r"
+ ))
+ (kmacro-tests-should-insert "142586"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 2)))
+ (set-register ?\C-r nil))
+
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-01 ()
+ "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+ (set-register ?\C-r 2) ;; Should be safe as a register name
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Increment counter if it matches
+ "\C-x\C-k\C-ra=\C-r"
+ ))
+ (kmacro-tests-should-insert "0134"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-02 ()
+ "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+ (set-register ?\C-r 2) ;; Should be safe as a register name
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Add two to counter if it matches
+ "\C-u2\C-x\C-k\C-ra=\C-r"
+ ))
+ (kmacro-tests-should-insert "0145"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-03 ()
+ "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+ (set-register ?\C-r 2) ;; Should be safe as a register name
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Add four to counter if it matches
+ "\C-u\C-x\C-k\C-ra=\C-r"
+ ))
+ (kmacro-tests-should-insert "0167"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-equal-04 ()
+ "`kmacro-reg-add-counter-equal' increments counter if equal to register"
+ (set-register ?\C-r 2) ;; Should be safe as a register name
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Decrement counter if it matches
+ "\C-u-\C-x\C-k\C-ra=\C-r"
+ ))
+ (kmacro-tests-should-insert "0111"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-less ()
+ "`kmacro-reg-add-counter-less' increments counter if less than register"
+ (set-register ?\C-r 6) ;; Should be safe as a register name
+ (kmacro-tests-simulate-command '(kmacro-set-counter 8))
+ (kmacro-tests-define-macro (vconcat
+ ;; Decrement counter if it's
+ ;; less than the register
+ "\C-u-1\C-x\C-k\C-ra<\C-r"
+ ;; Insert and decrement counter
+ "\C-u-\C-x\C-k\C-i"
+ ))
+ (kmacro-tests-should-insert "8764"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (set-register ?\C-r nil))
+
+(kmacro-tests-deftest kmacro-tests-test-reg-add-counter-greater ()
+ "`kmacro-reg-add-counter-greater' increments counter if greater than
register"
+ (set-register ?\C-r 2) ;; Should be safe as a register name
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Increment counter if it's greater
+ ;; than the register
+ "\C-x\C-k\C-ra>\C-r"
+ ))
+ (kmacro-tests-should-insert "0124"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (set-register ?\C-r nil))
+
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-equal-01 ()
+ "`kmacro-quit-counter-equal' stops macro if counter is equal to prefix"
+ (kmacro-tests-simulate-command '(kmacro-set-counter 5))
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and decrement counter
+ "\C-u-\C-x\C-k\C-i"
+ ;; Stop if the counter is at 0
+ "\C-x\C-k\C-q="
+ ))
+ (kmacro-tests-should-insert "5432"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (should (condition-case abort
+ (should (= 1 kmacro-counter))
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+ (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-equal-02 ()
+ "`kmacro-quit-counter-equal' stops macro if counter is equal to prefix"
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Stop if the counter is at 5
+ "\C-u5\C-x\C-k\C-q="
+ ))
+ (kmacro-tests-should-insert "0123"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (should (condition-case abort
+ (should (= 4 kmacro-counter))
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+ (quit abort))))
+
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-equal-03 ()
+ "`kmacro-quit-counter-equal' stops macro if counter is equal to prefix"
+ (kmacro-tests-simulate-command '(kmacro-set-counter 4))
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and decrement counter
+ "\C-u-\C-x\C-k\C-i"
+ ;; Stop if the counter is at -1
+ "\C-u-\C-x\C-k\C-q="
+ ))
+ (kmacro-tests-should-insert "4321"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (should (condition-case abort
+ (should (= 0 kmacro-counter))
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+ (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-less ()
+ "`kmacro-quit-counter-less' stops macro if counter is less than prefix"
+ (kmacro-tests-simulate-command '(kmacro-set-counter 8))
+ (kmacro-tests-define-macro (vconcat
+ ;; Stop if the counter is less than 5
+ "\C-u5\C-x\C-k\C-q<"
+ ;; Insert and decrement counter
+ "\C-u-\C-x\C-k\C-i"
+ ))
+ (kmacro-tests-should-insert "8765"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (should (condition-case abort
+ (should (= 4 kmacro-counter))
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+ (quit abort))))
+
+(kmacro-tests-deftest kmacro-tests-test-quit-counter-greater ()
+ "`kmacro-quit-counter-greater' stops macro if counter is greater than prefix"
+ (kmacro-tests-define-macro (vconcat
+ ;; Insert and increment counter
+ "\C-x\C-k\C-i"
+ ;; Stop if the counter is greater than 4
+ "\C-u4\C-x\C-k\C-q>"
+ ))
+ (kmacro-tests-should-insert "0123"
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 4)))
+ (should (condition-case abort
+ (should (= 4 kmacro-counter))
+ (kmacro-tests-simulate-command '(kmacro-end-or-call-macro 1))
+ (quit abort))))
+
+
(kmacro-tests-deftest kmacro-tests-end-call-macro-prefix-args ()
"kmacro-end-call-macro changes behavior based on prefix arg."
;; "Record" two macros.
Thanks!
--
Alex.
- bug#61549: 30.0.50; [PATCH] New keyboard macro counter functions,
Alex Bochannek <=