elisp: defmacro with lambda - emacs

Sorry, I tried few times and I can't build simple defmacro - can anybody help me with that:
So, I want define macro which will expand to:
(define-key keymap "*" (lambda ()
(interactive)
(some-func "*")))
I tried something like:
(defmacro wrap-and-define-key (keymap key func)
`(define-key ,keymap ,key (lambda () (interactive) (,func ,key))))
And it works with:
(wrap-and-define-key keymap key func)
but doesn't work with
(wrap-and-define-key keymap key 'func)
How to change defmacro statement to make last form working? Thanks!

You don't need a macro for this.
(defun wrap-and-define-key (keymap key func)
(define-key keymap key (lambda () (interactive) (funcall func key))))
(Don't quote lambda forms. Avoid eval.)
Not sure why you want this, but anyway, this should do what you ask.

The reason your macro doesn't work for the second example is 'func is passed as the argument with the quote, since arguments to macros are not evaluated automatically. So the lambda in the macro expands to:
(lambda () (interactive) ('func key))
But you cannot quote a function name when it is invoked in this way. Only a lambda or an unquoted symbol can appear as the first item in a function invocation expression. If you change the macro to this:
(defmacro wrap-and-define-key (keymap key func)
`(define-key ,keymap ,key (lambda () (interactive) (funcall ,func ,key))))
It will work. When the lambda is invoked, 'func gets evaluated to the plain func symbol and passed to funcall. funcall then looks up the symbol in the function namespace (Elisp is a Lisp-2) and invokes it.
As suggested in another answer, you can accomplish this with a function. This function will work regardless of whether lexical mode is enabled:
(defun wrap-and-define-key (keymap key func)
(define-key keymap key `(lambda () (interactive) (funcall ',func ,key))))

Ok, find something working but not sure - that is elegant solution:
(defmacro wrap-and-define-key (keymap key func)
`(define-key ,keymap ,key '(lambda () (interactive) (eval (list ,func ,key)))))

Related

Temporarily change a function variable in elisp

Update
This question is no longer feasible.
Turned out ivy-read doesn't return immediately as I expected. I used C-g to cancel completion which skips the restoring ivy-format-function part.
I'm writing an ivy extension with dynamic collection. I'd like to return a list of list of strings instead of a list of strings as candidates. The default value of ivy-format-function only supports list of strings so I decide to change it to my own function while calling ivy-read then change it back.
I defined the following macro and function:
(defun ivy-foo ()
(interactive)
(with--ivy-foo-format-function
(ivy-read "Foo: "
#'ivy-foo-function
:dynamic-collection t
:require-match t
:action #'ivy-foo--action
:unwind #'ivy-foo--unwind
:history 'ivy-foo-history
:caller 'ivy-foo)))
(defmacro with--ivy-foo-format-function (&rest body)
`(let ((original-function ivy-format-function))
(setq ivy-format-function (lambda (candidates) (ivy-foo--format-function candidates original-function)))
,#body
(setq ivy-format-function original-function)))
(defun ivy-foo--format-function (candidates original-format-function)
(funcall original-format-function
(mapcar
(lambda (cand)
(if (listp cand)
(car cand)
cand))
candidates)))
(defun ivy-foo-function (str)
(list (list "cand1" str) (list "cand2" str)))
with--ivy-foo-format-function doesn't set ivy-format-function to the original value. I got error "Symbol's value as a variable is void: original-function". What's wrong?
Update: ivy-format-function is a defvar. Its default value is #'ivy--format-function-default

Substitute symbol name in macro

How can I substitute a symbol name into a function created in a macro? I think I am missing something obvious here. For example, I am trying to make a macro that defines some variables and functions similar to the following,
(cl-defmacro mac (pkg)
(let (
;; Define some variables
(sym (intern (concat pkg "-file")))
(sym-def "default-file.el")
(sym-doc (concat "A custom var from `" pkg "'."))
;; Define some functions
(symfn (intern (concat pkg "-fn")))
(symfn-doc (concat "A function for `" pkg "'.")))
`(list
(defcustom ,sym ,sym-def ,sym-doc
:group (quote ,(make-symbol pkg))
:type '(choice (const :tag "None" nil)
file))
(defun ,symfn ()
,symfn-doc
(interactive)
(fn ,sym)))))
The function returned makes a call out to another function (fn) with a signature like
(defun fn (var) (symbol-value var))
So, it is expecting a call like (fn 'some-var). And, I want to be able to use the macro like
(mac "pack")
And have the following work,
pack-file ; works: "default-file.el"
(pack-fn) ; error: not a symbol
I have tried things like (quote ,sym), symbol-name, and others... But can't seem to get it right.
You want the call to fn to be (fn ',sym) (which you mention you tried in the question, but I suspect got wrong somehow).
You probably also want the expansion of the macro to be (progn ...) instead of (list ...).
(This was originally a comment: I'm putting it here just so there's an answer.)

emacs how to use call-interactively with parameter

If I want to make my own function which among other thing calls wg-save (workgroups.el - save workgroups) then I do something like this:
(defun foo ()
(interactive)
...
(call-interactively 'wg-save)
)
(global-set-key (kbd "my binding") 'foo)
What about the following scenario (I will use eyebrowse.el as an example):
eyebrowse uses C-c C-w 'number' to move to different window configurations e.g. C-c C-w 1 to move to 1 or C-c C-w 2 to move to 2.
How can I write a similar function like the 'foo' since now I need to pass to 'call-interactively' a 'number' as parameter?
EDIT: C-c C-w 1 calls eyebrowse-switch-to-window-config-1.
So I need to make a 'foo' function like the above that will 'call-interactively'
'eyebrowse-switch-to-window-config-1' when the key binding is 'C-c C-w 1', 'eyebrowse-switch-to-window-config-2' when the key binding is 'C-c C-w 2' etc.
Something like the following (if it makes sense):
(defun foo ()
(interactive)
...
(call-interactively 'eyebrowse-switch-to-window-config-"number")
)
(global-set-key (kbd "C-c C-w 'number'") 'foo)
From the documentation, read with C-h f call-interactively RET:
Optional third arg KEYS, if given, specifies the sequence of events to
supply, as a vector, if the command inquires which events were used to
invoke it. If KEYS is omitted or nil, the return value of
`this-command-keys-vector' is used.
So to pass arguments to call-interactively, give it a vector with the arguments, like so
(call-interactively 'my-fn t (vector arg1 arg2))
That way you don't need to call eyebrowse-switch-to-config-window-nwhere n is a number, you call the function they rely on, eyebrowse-switch-to-window-config, and give it a number as argument.
You can get a number for argument like this: (see help of "interactive" )
(defun foo (arg)
(interactive "nWindow? ")
(call-interactively 'my-fn t (vector arg))
)
But did you read the source of eyebrowse ? It will give ideas.
(defun eyebrowse-switch-to-window-config-0 ()
"Switch to window configuration 0."
(interactive)
(eyebrowse-switch-to-window-config 0))
You can do something like this I believe:
(defun foo (NUM)
"Enter the NUM of the screen to go to.
If the key pressed is not a number go to screen 0."
(interactive
(list (read-key-sequence "Which window: ")))
((eyebrowse-switch-to-window-config 3) (string-to-number NUM)))
The key is the read-key-sequence function
To save time and space, you can use a loop to define all the keybindings you'll use.
For example, for eyebrowse instead of defining each keybinding you could do this instead:
(load-library "eyebrowse")
(dotimes (i 10)
(global-set-key
(kbd (concat "C-c C-w " (number-to-string i)))
`(lambda ()
(interactive)
(eyebrowse-switch-to-window-config ,i))))

Elisp: call keymap from code?

Here's a minimal snippet to get things going:
(define-prefix-command 'foo)
(define-key foo "a" 'bar)
(define-key foo "b" 'baz)
(global-set-key (kbd "C-M-r") 'foo)
Now I can "call" the foo keymap when I press C-M-r.
But I wonder how I can do this from code, e.g. something like:
(funcall (lambda-from-keymap 'foo))
After this call, I expect the focus to be in the minibuffer, expecting either
a, or b or C-h to be entered.
Is something like this possible?
You can use read-key-sequence and lookup-key to implement this:
(defun call-keymap (map &optional prompt)
"Read a key sequence and call the command it's bound to in MAP."
;; Note: MAP must be a symbol so we can trick `describe-bindings' into giving
;; us a nice help text.
(let* ((overriding-local-map `(keymap (,map . ,map)))
(help-form `(describe-bindings ,(vector map)))
(key (read-key-sequence prompt))
(cmd (lookup-key map key t)))
(if (functionp cmd) (call-interactively cmd)
(user-error "%s is undefined" key))))
If you hit C-h read-key-sequence still waits for you to complete the sequence. I think you could simulate Emacs' normal behaviour by looping with read-key instead, it's a bit more involved though.
Use it like this:
(defun bar () (interactive) (message "you called bar"))
(defun baz () (interactive) (message "you called baz"))
(define-prefix-command 'foo)
(define-key foo "a" 'bar)
(define-key foo "b" 'baz)
(global-set-key (kbd "C-M-r") 'foo)
(defun call-foo ()
(interactive)
;; Note: pass the symbol form of the keymap so we can give nice help
(call-keymap 'foo "enter a foo command: "))
(global-set-key (kbd "C-c f") 'call-foo)
If the keymap is bound to a key sequence, you can invoke it by emulating the exact key sequence by setting unread-command-events:
(setq unread-command-events
(mapcar (lambda (e) `(t . ,e))
(listify-key-sequence (kbd "C-M-r"))
You need to have foo interactive. I did so using:
(global-set-key (kbd "C-M-r") (lambda () (interactive) ( foo)))
This should fix your problem.

Wrong type error when evaluating a function bound to key in emacs

I have written a function init-set-key-mappings that sets all the global keys in .emacs file.
(defun init-set-key-mappings ()
"All the key mappings go here"
(let ((mappings (list
'("\C-ca" 'open-fileline))))
(mapcar (lambda (mapping)
(let ((key (car mapping))
(func (cadr mapping)))
(progn
(message (format "map key %s to %s" key func))
(global-set-key key func))))
mappings)))
It evaluates fine, but when I press C-c a, Emacs complains "Wrong type argument commandp, (quote open-fileline)"
What am I doing wrong?
EDIT: I found the answer. Removing the quote before open-file seems to fix the problem. Why
is it being a symbol the problem? Isn't this how functions are passed around - as symbols?
You double-quoted the function open-fileline. In the expression
(list '("\C-ca" 'open-fileline)))
the first quote indicates that everything in the following list is quoted. You then added a second quote to open-fileline. Which means the list doesn't actually contain a symbol as the second element of it's car, but a quoted symbol. Compare:
(symbolp (cadar (list '("string" 'open-fileline))))
and
(symbolp (cadar (list '("string" open-fileline))))