0

I have this code:


(defmacro with-ignore-mouse-events (&rest body)
  "Macro to ignore mouse events before evaluating BODY."
  `(progn
     (when (input-pending-p)
       (let ((event (read-event)))
         (if (mouse-event-p event)
             (let ((button (event-basic-type event)))
               (if (eq button 'mouse-1)
                   (ignore)
                 (setq unread-command-events (list event))))
           (setq unread-command-events (list event)))))
     ,@body))

(defun mwe-function-1 () "MWE function 1" (interactive) (unwind-protect (query-replace "foo" "bar" nil (point-min) (point-max)) ;; UNWINDFORMS (read-string "function 1 executed")))

(defun mwe-function-2 () "MWE function 3" (interactive) (unwind-protect (query-replace "foo" "bar" nil (point-min) (point-max)) ;; UNWINDFORMS (read-string "function 2 executed")))

(defun mwe-function-3 () "MWE function 3" (interactive) (unwind-protect (query-replace "foo" "bar" nil (point-min) (point-max)) ;; UNWINDFORMS (read-string "function 3 executed")))

(defvar mwe-function-names '(mwe-function-1 mwe-function-2 mwe-function-3))

I want to generate modified version of the functions like in the following example:

(defun mwe-function-1* ()
  (interactive)
  (with-ignore-mouse-events
   (mwe-function-1)))

I tried:

(defmacro generate-mwe-star-functions (&rest function-names)
  "Macro to generate mwe-function-* functions."
  `(dolist (func-name ,@function-names)
    (message "%s*" func-name) ;; The string built here is correct...
    (defun ,(make-symbol (format "%s*" func-name)) ()
      (interactive)
      (with-ignore-mouse-events
       (intern func-name)))
    ))

(generate-mwe-star-functions mwe-function-names)

But I'm getting lost in the mechanism of back quoting (at least).

Where am I wrong?

Edit. The question suggested as a duplicated of mine does not explain how to loop a list of functions to define other functions. I thing this make the task a bit more complicated.

This is evidenced by the fact that the solution proposed by the user who (probably) flagged my question as a duplicate does not work.

Drew
  • 77,472
  • 10
  • 114
  • 243
Gabriele
  • 1,554
  • 9
  • 21

2 Answers2

2

Finally I found a solution:

(defun generate-mwe-star-functions (functions)
  "Generate starred versions of the given functions, where original
functions are wrapped with the `with-ignore-mouse-events' macro"
  (dolist (func functions)
    (let* ((func-name (symbol-name func))
       (starred-func-name (intern (format "%s*" func-name))))
      (defalias starred-func-name
        `(lambda ()
           (interactive)
           (with-ignore-mouse-events
             (funcall ',func)))
    (format "Starred version of the function `%s', where the original function is wrapped with the `with-ignore-mouse-events' macro" func-name)))))

I followed db48x's suggestion, using defun instead of defmacro, and Phil's suggestion to use defalias instead of fset in order to add a docstring to the functions.

Gabriele
  • 1,554
  • 9
  • 21
  • 1
    I suggest quoting %s in the docstring: \%s'` (so that the final version has a quoted function name). – phils Feb 19 '24 at 22:50
  • Good idea. Thanks! – Gabriele Feb 19 '24 at 23:07
  • Also note that IFF you're using lexical-binding for your library then you could skip the backquote/unquote, as func would already be a lexical variable for the lambda, and so (funcall func) would be sufficient. That ought to have byte-compilation benefits (the macro would need to be known at compile time). – phils Feb 19 '24 at 23:25
  • I think I'll need to delve deeper into this topic. Thank you. – Gabriele Feb 19 '24 at 23:32
1

There’s no reason at all for this to be a macro. Just make it an ordinary function. If you really want to fix the quoting, search for duplicate questions about quoting in macros; it is a very common question.

Edit:

The answer you mentioned in the comments has the perfect solution for you. Instead of using defun inside of a macro, use fset inside of a function. Like this:

(defun generate-mwe-star-functions (function-names)
  "Generate mwe-function-* functions.  FUNCTION-NAMES is a list of symbols."
  (dolist (func-name function-names)
    (fset (make-symbol (format "%s*" (symbol-name func-name)))
          (lambda ()
            (interactive)
            (with-ignore-mouse-events
              (funcall func-name))))))

If you really want to use a macro for some reason, the main problem is that you compute func-name inside the code generated by the macro, but try to substitute it into the same code with a comma. Everything that is substituted in with a comma is evaluated when the macro is compiled, and the code inside the backquote is only called later when the code is run. Thus you either need to compute func-name outside the backquote so that it exists to be substituted in with a comma, or you need to remove the comma. Which you prefer is up to you.

You can no doubt find a dozen other people asking the same macro question where the answer is to move something out of the backquoted code.

db48x
  • 17,977
  • 1
  • 22
  • 28
  • 1 - Your response seems more like a comment than an answer to me. 2 - Of course, I did some research before writing this question, and one of the things I found in these searches is that many recommend using macros for this kind of operations. Take a look here: https://emacs.stackexchange.com/q/70483/15606 3 - If I were able to solve my problem on my own, I wouldn't have written this question. I've spent several hours trying different approaches. – Gabriele Feb 18 '24 at 23:01
  • Thanks. I tried your function this way (generate-mwe-star-functions mwe-function-names) but it doesn't work. No new function is defined. At least not with the expected names. – Gabriele Feb 18 '24 at 23:48
  • Did you see the part where I changed the argument to a list of symbols, instead of a list of strings? – db48x Feb 18 '24 at 23:55
  • In my question I use (defvar mwe-function-names '(mwe-function-1 mwe-function-2 mwe-function-3)) that I think is a list of symbols. Where did you see a list of string in my question? – Gabriele Feb 19 '24 at 00:04
  • Oh, I just went off of the fact that you fed func-name directly to concat which only accepts strings. The problem must be somewhere else then. Run (trace-function 'fset) to log every call to fset. When you run generate-mwe-star-functions it will pop up a log window to show them to you. – db48x Feb 19 '24 at 00:12
  • Wasn't it supposed to be an easy problem to solve? ;-) I ran (trace-function 'fset) and then (generate-mwe-star-functions mwe-function-names) but no log window appeared. – Gabriele Feb 19 '24 at 00:24
  • If you don’t have a “trace-output” window somewhere then something horrible has gone wrong and you have bigger fish to fry. – db48x Feb 19 '24 at 00:28
  • Also, I’m leaving for the evening. Good luck. – db48x Feb 19 '24 at 00:30
  • 1
    As we're talking about interactive functions, I'd suggest using defalias rather than fset for ease of generating and adding a docstring for each of the new functions. (Each docstring can trivially say that it's a version of \'which is wrapped by`with-ignore-mouse-events'`.) – phils Feb 19 '24 at 01:19