passing function arguments in elisp - emacs

I have a function defined in my init.el:
(defun bind-key (keymap)
(evil-define-key '(normal insert) keymap (kbd "C-=") 'some-function))
(bind-key 'c++-mode-map)
But evil-define-key does not bind the C-= to some-function in keymap.
However, invoke evil-define-key directly is ok:
(evil-define-key '(normal insert) c++-mode-map (kbd "C-=") 'some-function)
I have tried:
(bind-key 'c++-mode-map)
(bind-key c++-mode-map)
Neither works.
I have googled for passing keymaps to a function, but found no solution. Then, I noticed evil-define-key is a macro. But I can not found solutions in this situation.
How can I get bind-key to work? By passing a keymap to it, the function binds C-= to some-function in in the keymap?

As you've noticed, this is trickier than it looks because evil-define-key is a macro (defined here). It takes a symbol that names a keymap variable, and binds the key once that variable has been defined. However, in this case it gets the symbol keymap instead of c++-mode-map, since a macro invocation receives as arguments the literal values in the call.
You can get around this by changing your own function into a macro. That means that instead of just running some code, it needs to return some code that then gets evaluated. Like this:
(defmacro bind-key (keymap)
`(evil-define-key '(normal insert) ,keymap (kbd "C-=") 'some-function))
The backquote introduces a form that gets returned verbatim, except for values inside it preceded by a comma.
Invoke it with (bind-key c++-mode-map), and it should be equivalent to your explicit call to evil-define-key.

Related

Run function before keymap commands

Is there a way to advise a keymap or otherwise run a function after a keymap prefix, but before commands in the keymap?
Say I have a keymap with bindings for hideshow, but these bindings are only useful after hs-minor-mode is activated. How can I run (hs-minor-mode) after the prefix is entered, but before the functions in the map are called?
I thought making a prefix command and advising it might work, but that is an error (below).
Example:
(let ((map (define-prefix-command 'my-activate-fold 'my-fold-map)))
(define-key map "a" #'hs-hide-all)
(define-key map "s" #'hs-show-all)
(define-key map "l" #'hs-hide-level)
(global-set-key (kbd "<f6>") 'my-activate-fold))
;; error: wrong-type-argument commandp my-activate-fold
(define-advice my-activate-fold (:before (fn &rest r) "activate-hideshow")
(hs-minor-mode)
(apply fn r))
If I understand correctly, I think you may be approaching this incorrectly. For your specific question, no, there is no way to define a function which will run after a prefix key is called but before the command (an interactive function) which is bound to the key is run. However, I'm not sure that is really what your after. You can of course define commands which can wrap another command and do whatever you want. However, I'm not sure that is what you want either. You might want to state exactly what you want rather than part of what you believe is the solution to what you want.
Normal practice is for a minor mode to define a keymap and you add your mode specific key bindings to that map in a mode initialisation hook. In the case of hs-minor-mode, that is called hs-minor-mode-map. This map only exists inside buffers running hs-minro-mode and it takes precedence over the global map. So this is where you want to place your mode specific bindings. To do this, create a function which adds the bindings to the map and attach that to the hs-minor-mode-hook, which is run when hs-minor-mode is loaded.
(add-hook 'hs-minor-mode-hook (lambda ()
(define-key 'hs-minor-mode-map "a" #'hs-hide-all)
...))
The key bindings defined above will only exist if hs-minor-mode is active in the buffer. If it is not active, the keys used in the binding will either be bound to whatever the next highest map in the mode is or the global map or nothing (see the section on keymaps in the elsip manual for full details).
If what you want is to have specific hs-minor-mode bindings only exist in specific modes, then you can define those bindings in the keymap for that mode. For example, if you wanted hs-minor-mode bindings that only exist when you run hs-minor-mode in js2-mode, but not when you run hs-minor-mode in c-mode, then you can add the bindings to the js2-mode-map and load hs-minor-mode as part of the js2-mode-hook
If it is something else you are after, you need to clarify.
FWIW, you could simply advise the commands themselves:
(defun my-ensure-hs-minor-mode (&rest _args)
"Ensure `hs-minor-mode' is active."
(unless (bound-and-true-p hs-minor-mode)
(hs-minor-mode 1)))
(advice-add 'hs-hide-all :before #'my-ensure-hs-minor-mode)
(advice-add 'hs-show-all :before #'my-ensure-hs-minor-mode)
(advice-add 'hs-hide-level :before #'my-ensure-hs-minor-mode)
Your keymap then just works:
(require 'hideshow)
(let ((map (define-prefix-command 'my-activate-fold 'my-fold-map)))
(define-key map "a" #'hs-hide-all)
(define-key map "s" #'hs-show-all)
(define-key map "l" #'hs-hide-level))
(global-set-key (kbd "<f6>") 'my-activate-fold)

Why does key binding cause Emacs to execute my function on startup?

I have a function in my Emacs init.el file that lets me rebuild and byte-compile it from a literate source file. It consists of a lambda function wrapped by defun and works exactly as I expect. So far, so good.
(defun tangle-init-and-reload ()
"Tangle the code blocks in init.org, byte-compile, and reload."
(lambda ()
(interactive)
;; Don't run hooks when tangling.
(let ((prog-mode-hook nil))
(org-babel-tangle-file (concat user-emacs-directory "init.org"))
(byte-compile-file (concat user-emacs-directory "init.el"))
(load-file user-init-file))))
When I read about functions in Elisp, it appears to me that I should be able to simply use defun to define a named function and skip the lambda, so I removed the lambda and otherwise left the function intact, like so:
(defun tangle-init-and-reload ()
"Tangle the code blocks in init.org, byte-compile, and reload."
(interactive)
;; Don't run hooks when tangling.
(let ((prog-mode-hook nil))
(org-babel-tangle-file (concat user-emacs-directory "init.org"))
(byte-compile-file (concat user-emacs-directory "init.el"))
(load-file user-init-file)))
Written this way, the function also works as expected -- as long as I call it with M-x tangle-init-and-reload RET. If I assign it a key binding, it executes on startup with one of two different side effects: With some key bindings, it attempts to overwrite init.elc while Emacs still has it open, and with others it successfully overwrites init.elc, but then re-executes on reload, causing an infinite recursion.
I'm perfectly happy to stick with the lambda version, which has no issues with key binding, but I would like to understand what magic lambda is performing and/or what it is about key binding that causes the second version to execute at startup. Can anybody explain?
For whatever it's worth, my key bindings are in a custom minor mode like so:
(defvar custom-map (make-keymap)
"Custom key bindings.")
(define-key custom-map (kbd "C-c C-i") (tangle-init-and-reload))
(define-minor-mode custom-bindings-mode
"Activates custom key bindings."
t nil custom-map)
When you define the key binding, you associate a key to a value, which in your case is:
(tangle-init-and-reload)
This is an expression that is evaluated normally, ie. you call the function when you associate the binding.
In the previous version, evaluating the same function returned a closure, you had one level of indirection, so you established a binding from a key to the function returned by the call to tangle-init-and-reload.
You can simply give the name of the function associated with the binding, by quoting it:
(define-key custom-map (kbd "C-c C-i") 'tangle-init-and-reload)

Emacs: custom function and keybinding for commenting out line

I have been trying out stuff and looking at other answers for several hours now and I cannot figure out how to make custom functions and keybindings work... and it is absolutely infuriating.
For the purposes of testing I wrote this function
(defun my/cmmt ()
""
(interactive)
(move-beginning-of-line 1)
(comment-region 1))
(global-set-key (kbd "\C-o")
'my/cmmt)
There are 2 issues, I want it bind this to C-m but then I get an error:
symbol's value as variable is void: C-m
What does that mean?
And also, all it ever does is move the cursor to the beginning of the line, but not comment it out. Why?
Edit
(defun my/cmmt ()
""
(interactive)
(comment-region
(line-beginning-position)
(line-end-position)
)
)
(global-set-key (kbd "C-o")
'my/cmmt)
Now the error is:
symbol's function definition is void: \,
(kbd "C-o") not (kbd "\C-o")
You're confusing two methods of specifying keys -- (kbd "C-o") and "\C-o" are equivalent.
I recommend using kbd, and simply typing C-hk keys to learn what to pass to kbd to specify the key sequence keys. e.g.: when you type C-hkC-o Emacs tells you that C-o is the representation of that key sequence, so "C-o" is what you must pass to kbd.
The reason the commenting doesn't work is because (comment-region 1) isn't valid. You should be seeing an error. It takes two required arguments. See C-hf comment-region for details.

Elisp code to bind a keychord to another keychord

In Emacs, with the idea of binding a keychord to another keychord, why is this elisp code not working?
(global-set-key (kbd "C-f") (kbd "C-s"))
(The idea being, in this example, to get "C-f" to do the same as "C-s".)
I don't know why it not work either, but the usual way to achieve your purpose is just binding a key to a interactive command, instead of a string or keyboard macro, in this case (kbd "C-s"):
(global-set-key (kbd "C-f") #'isearch-forward)
or (the above is better)
(global-set-key (kbd "C-f") (key-binding (kbd "C-s")))
According to the documentation, I was expecting the following to be true: What you are attempting doesn't work, because you're binding the result of (kbd "C-s") to a key, which is an internal Emacs key representation and not the function that is (globally) set to the key. However, as xuchunyang pointed out, that's not entirely correct. global-set-key calls define-key internally which has in its documentation the following part:
(define-key KEYMAP KEY DEF)
In KEYMAP, define key sequence KEY as DEF. KEYMAP is a keymap.
KEY is a string or a vector of symbols and characters, representing a
sequence of keystrokes and events. [...]
DEF is anything that can be a key's definition:
nil (means key is undefined in this keymap),
a command (a Lisp function suitable for interactive calling),
a string (treated as a keyboard macro), [...]
And indeed, xunchunyang's example (global-set-key [?l] (kbd "C-f") works as you expect. (global-set-key [?l] (kbd "C-s") doesn't work, because isearch-forward expects an argument (the search regexp) which interactive will ask for. Only if you provide one, this particular keyboard macro will work, i.e. (global-set-key [?l] (concat (kbd "C-s") "foo")) will bind a search for "foo" to the key "l".
xuchunyang's answer is absolutely right: the best way is to name the function explicitly via (global-set-key (kbd "C-f") #'isearch-forward).
You can easily figure out the function that is bound to a key by typing C-h k and then the key which you want to "copy": e.g. C-h k C-s) will show you that C-s is (usually, cf. below) bound to isearch-forward.
The approach using key-binding could, in the general case, lead to strange results: depending on where and how you execute this (i.e. interactively in some buffer or in your .emacs), results may differ, because key-binding honours current keymaps, which might assign different functions to keys.

Why is (commandp '(customize-option 'foo)) nil?

I want to bind customize-option for a certain variable to a key, since I need to change it rather often. I have two options:
(global-set-key (kbd "<f12>") '(lambda() (interactive) (customize-option 'my-variable) ) )
(global-set-key (kbd "<f12>") '(customize-option 'my-variable )
The first one works, the second does not, because commandp complains that customize-option is not a command. Why? As far as I know, customize-option is an interactive function, so commandp should be t:
customize-option is an interactive compiled Lisp function.
It is bound to .
(customize-option SYMBOL)
Customize SYMBOL, which must be a user option variable.
It is the form (customize-option 'my-variable) which is not a command. You cannot bind to an arbitrary quoted form, any more than you can bind to a literal string or an unbound symbol. Some of those would be useful to bind to, but it's not hard to work around the limitations. Write a macro if you find it hard to live with. (As the saying goes, now you have two problems.)
The second argument to global-set-key must be a command definition, typically a symbol naming an interactive function. An interactive function is a function that begins with the (interactive) form. For example:
(defun delete-to-end ()
"Delete text from point to the end of buffer."
(interactive)
(delete-region (point) (point-max)))
This defines an interactive function and assigns it to the symbol delete-to-end. After that, delete-to-end is a valid command that you can pass to global-set-key:
(global-set-key [f12] 'delete-to-end)
Without the (interactive) line, delete-to-end would still name a function callable from Lisp programs, but it would not be a "command". Since it is marked interactive, (commandp 'delete-to-end) returns true, and M-x delete-to-end works.
Interactive functions don't need to be bound to a symbol, they can be anonymous. Like any other anonymous functions, they are created using a lambda form, except that for commands it must also include (interactive). Anonymous commands can be passed as the second argument to global-set-key without assigning them to a symbol, so the following definition is equivalent to the one above:
(global-set-key [f12]
(lambda ()
"Delete text from point to the end of buffer."
(interactive)
(delete-region (point) (point-max))))
...except it's somewhat less readable, and looks uglier when inspected with C-h c
or C-h k.
In your case, the first call to global-set-key is given a valid command (a quoted lambda form is itself a valid function), but the second one isn't, it is given a two-element list that can neither be called nor satisfies the requirement of being marked "interactive".