Run function before keymap commands - emacs

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)

Related

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)

Mode-specific or buffer-local text objects in evil?

Q: in Emacs, how does one make mode-specific key bindings for text objects in evil?
One may bind a key in a specific state (normal, insert, etc.) in a specific mode as the following example demonstrates:
(evil-define-key 'normal org-mode "a" 'some-command)
However, it's not clear to me how to bind a key in a mode-specific way to evil-outer-text-objects-map (or its -inner- counterpart). As an alternative, it's also not clear how one might bind keys in these maps in a buffer-local way through a mode hook.
It doesn't look like evil-local-set-key will do it, because it expects a state (normal, insert, etc.) as its first argument, and that's not relevant to this task.
It's also not clear how to use local-set-key in this instance, because it expects a key and a command as arguments, but does not take a map as an argument.
This is kinda late but for the first part of your question you can use local maps like:
(defun my-elisp-mode-configuration ()
(with-eval-after-load 'evil
(define-key evil-visual-state-local-map "ie" 'sp-evil-i-sexp)
(define-key evil-operator-state-local-map "ie" 'sp-evil-i-sexp)))
(add-hook 'emacs-lisp-mode-hook #'my-elisp-mode-configuration)
In this example I bind 'inner' e operator to a custom sp-evil-i-sexp text object for elisp mode only.
Coming to your second question; evil overrides local maps so using local-set-key won't suffice. Instead you can use:
local evil maps with hooks; like evil-normal-state-local-map as in the previous example
use evil-define-key; ie: (evil-define-key 'normal emacs-lisp-mode-map (kbd " ") 'my-leader)
Note that you can not override Evil's bindings this way but those unemployed or employed at a global or local level. Use the first method in case you want to override Evil bindings.
And there is evil-make-overriding-map which causes local-map to override Evil's bindings but this is rarely what you want because you want hjkl to work at least but useful for modes like dired which evil makes less sense.
Footnote: There is nothing special about Evil's operators or text objects from Emacs's perspective. They're just bound keymaps. Eg: i key is bound to the evil-inner-text-objects-map which includes text objects like w as in:
(define-key evil-visual-state-map "i" evil-inner-text-objects-map)
(define-key evil-inner-text-objects-map "w" 'evil-inner-word)
You can find these lines in evil-maps.el
When reading the mailing list someone mentioned it is better to put keybindings in eval-after-load instead of hooks, so here it is:
(eval-after-load "<mode>"
'(progn
<object-definition>))
As for defining new text objects, I must recommend this function from #gordon-gustafson:
(defmacro define-and-bind-text-object (key start-regex end-regex)
(let ((inner-name (make-symbol "inner-name"))
(outer-name (make-symbol "outer-name")))
`(progn
(evil-define-text-object ,inner-name (count &optional beg end type)
(evil-select-paren ,start-regex ,end-regex beg end type count t))
(evil-define-text-object ,outer-name (count &optional beg end type)
(evil-select-paren ,start-regex ,end-regex beg end type count nil))
(define-key evil-inner-text-objects-map ,key (quote ,inner-name))
(define-key evil-outer-text-objects-map ,key (quote ,outer-name)))))
So the <object-definitions> part would become:
(define-and-bind-text-object "<key>" "<start-regex>" "<end-regex>")

Predicate-based dynamic key binding with default fallback [duplicate]

I'm trying to write a custom tab completion implementation which tries a bunch of different completions depending on where the point is. However, if none of the conditions for completions are met I would like tab to do what ever the current mode originally intended it to do.
Something like this:
(defun my-custom-tab-completion ()
(interactive)
(cond
(some-condition
(do-something))
(some-other-condition
(do-something-else))
(t
(do-whatever-tab-is-supposed-to-do-in-the-current-mode))) ;; How do I do this?
Currently I'm checking for specific modes and doing the right thing for that mode, but I really would like a solution that just does the right thing without me having to explicitly add a condition for that specific mode.
Any ideas of how to do this?
Thanks! /Erik
BTW, here is another solution:
(define-key <map> <key>
`(menu-item "" <my-cmd> :filter ,(lambda (cmd) (if <my-predicate> cmd))))
Here is a macro I wrote based on Emacs key binding fallback to define a keybinding conditionally. It adds the keybinding to the specified minor mode but if the condition is not true, the previously assigned action is executed:
(defmacro define-key-with-fallback (keymap key def condition &optional mode)
"Define key with fallback. Binds KEY to definition DEF in keymap KEYMAP,
the binding is active when the CONDITION is true. Otherwise turns MODE off
and re-enables previous definition for KEY. If MODE is nil, tries to recover
it by stripping off \"-map\" from KEYMAP name."
`(define-key ,keymap ,key
(lambda () (interactive)
(if ,condition ,def
(let* ((,(if mode mode
(let* ((keymap-str (symbol-name keymap))
(mode-name-end (- (string-width keymap-str) 4)))
(if (string= "-map" (substring keymap-str mode-name-end))
(intern (substring keymap-str 0 mode-name-end))
(error "Could not deduce mode name from keymap name (\"-map\" missing?)"))))
nil)
(original-func (key-binding ,key)))
(call-interactively original-func))))))
Then I can do things like the following to use the special binding for TAB only when I am on a header in outline-minor-mode. Otherwise my default action (I have both indent and yasnippets) is executed:
(define-key-with-fallback outline-minor-mode-map (kbd "TAB")
(outline-cycle 1) (outline-on-heading-p))
You could use functions such as key-binding (or its more specific variants global-key-binding, minor-mode-key-binding and local-key-binding) to probe active keymaps for bindings.
For example:
(call-interactively (key-binding (kbd "TAB")))
;; in an emacs-lisp-mode buffer:
;; --> indent-for-tab-command
;;
;; in a c++-mode buffer with yas/minor-mode:
;; --> yas/expand
One way to avoid infinite loops if your command is bound to TAB could be to put your binding in a minor mode, and temporarily disable its keymap while looking for the TAB binding:
(define-minor-mode my-complete-mode
"Smart completion"
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd "TAB") 'my-complete)
map))
(defun my-complete ()
(interactive)
(if (my-condition)
(message "my-complete")
(let ((my-complete-mode nil))
(call-interactively (key-binding (kbd "TAB"))))))
It's possible that you could achieve this without any special workarounds at all. In most modes TAB just does indentation by default, but if you set the global variable tab-always-indent to 'complete it will try to do completion first, and indent if no completion is possible. This usually works really well, although if TAB is bound to another command in one of your major modes you might be out of luck.
If that works in the modes you need, you'll just need to add your custom completion function to the front of the list completion-at-point-functions in all applicable buffers (maybe using a mode hook). The completion-at-point command calls each function listed in completion-at-point-functions until one of them returns non-nil, so all you need to do to have your custom completion function "fall through" to the existing behavior is return nil from it.
This isn't a 100% answer to the question, but if the major modes you're working with are written according to the normal guidelines it might be the cleanest way.
define-key can accept quoted string or interactive lambdas like in this example.
;Static
(define-key evil-normal-state-mapr "m" 'evil-motion-state)
;Conditional
(define-key evil-normal-state-map "m"
(lambda () (interactive) (message "%s" major-mode)))
Lambda's can be replaced with named functions like my-tab-completion and used more effectively.
From define-key's docstring (Emacs 25)
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),
a keymap (to define a prefix key),
a symbol (when the key is looked up, the symbol will stand for its
function definition, which should at that time be one of the above,
or another symbol whose function definition is used, etc.),
a cons (STRING . DEFN), meaning that DEFN is the definition
(DEFN should be a valid definition in its own right),
or a cons (MAP . CHAR), meaning use definition of CHAR in keymap MAP,
or an extended menu item definition.
(See info node `(elisp)Extended Menu Items'.)

Elisp: Conditionally change keybinding

I'm trying to write a custom tab completion implementation which tries a bunch of different completions depending on where the point is. However, if none of the conditions for completions are met I would like tab to do what ever the current mode originally intended it to do.
Something like this:
(defun my-custom-tab-completion ()
(interactive)
(cond
(some-condition
(do-something))
(some-other-condition
(do-something-else))
(t
(do-whatever-tab-is-supposed-to-do-in-the-current-mode))) ;; How do I do this?
Currently I'm checking for specific modes and doing the right thing for that mode, but I really would like a solution that just does the right thing without me having to explicitly add a condition for that specific mode.
Any ideas of how to do this?
Thanks! /Erik
BTW, here is another solution:
(define-key <map> <key>
`(menu-item "" <my-cmd> :filter ,(lambda (cmd) (if <my-predicate> cmd))))
Here is a macro I wrote based on Emacs key binding fallback to define a keybinding conditionally. It adds the keybinding to the specified minor mode but if the condition is not true, the previously assigned action is executed:
(defmacro define-key-with-fallback (keymap key def condition &optional mode)
"Define key with fallback. Binds KEY to definition DEF in keymap KEYMAP,
the binding is active when the CONDITION is true. Otherwise turns MODE off
and re-enables previous definition for KEY. If MODE is nil, tries to recover
it by stripping off \"-map\" from KEYMAP name."
`(define-key ,keymap ,key
(lambda () (interactive)
(if ,condition ,def
(let* ((,(if mode mode
(let* ((keymap-str (symbol-name keymap))
(mode-name-end (- (string-width keymap-str) 4)))
(if (string= "-map" (substring keymap-str mode-name-end))
(intern (substring keymap-str 0 mode-name-end))
(error "Could not deduce mode name from keymap name (\"-map\" missing?)"))))
nil)
(original-func (key-binding ,key)))
(call-interactively original-func))))))
Then I can do things like the following to use the special binding for TAB only when I am on a header in outline-minor-mode. Otherwise my default action (I have both indent and yasnippets) is executed:
(define-key-with-fallback outline-minor-mode-map (kbd "TAB")
(outline-cycle 1) (outline-on-heading-p))
You could use functions such as key-binding (or its more specific variants global-key-binding, minor-mode-key-binding and local-key-binding) to probe active keymaps for bindings.
For example:
(call-interactively (key-binding (kbd "TAB")))
;; in an emacs-lisp-mode buffer:
;; --> indent-for-tab-command
;;
;; in a c++-mode buffer with yas/minor-mode:
;; --> yas/expand
One way to avoid infinite loops if your command is bound to TAB could be to put your binding in a minor mode, and temporarily disable its keymap while looking for the TAB binding:
(define-minor-mode my-complete-mode
"Smart completion"
:keymap (let ((map (make-sparse-keymap)))
(define-key map (kbd "TAB") 'my-complete)
map))
(defun my-complete ()
(interactive)
(if (my-condition)
(message "my-complete")
(let ((my-complete-mode nil))
(call-interactively (key-binding (kbd "TAB"))))))
It's possible that you could achieve this without any special workarounds at all. In most modes TAB just does indentation by default, but if you set the global variable tab-always-indent to 'complete it will try to do completion first, and indent if no completion is possible. This usually works really well, although if TAB is bound to another command in one of your major modes you might be out of luck.
If that works in the modes you need, you'll just need to add your custom completion function to the front of the list completion-at-point-functions in all applicable buffers (maybe using a mode hook). The completion-at-point command calls each function listed in completion-at-point-functions until one of them returns non-nil, so all you need to do to have your custom completion function "fall through" to the existing behavior is return nil from it.
This isn't a 100% answer to the question, but if the major modes you're working with are written according to the normal guidelines it might be the cleanest way.
define-key can accept quoted string or interactive lambdas like in this example.
;Static
(define-key evil-normal-state-mapr "m" 'evil-motion-state)
;Conditional
(define-key evil-normal-state-map "m"
(lambda () (interactive) (message "%s" major-mode)))
Lambda's can be replaced with named functions like my-tab-completion and used more effectively.
From define-key's docstring (Emacs 25)
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),
a keymap (to define a prefix key),
a symbol (when the key is looked up, the symbol will stand for its
function definition, which should at that time be one of the above,
or another symbol whose function definition is used, etc.),
a cons (STRING . DEFN), meaning that DEFN is the definition
(DEFN should be a valid definition in its own right),
or a cons (MAP . CHAR), meaning use definition of CHAR in keymap MAP,
or an extended menu item definition.
(See info node `(elisp)Extended Menu Items'.)

emacs, python-mode.el define-key map assignment

When I load the python-mode.el file in emacs, I don't get any of the key bindings specified.
I started playing with the file and noticed that when I change:
(define-key map [(control c)(\#)] 'py-comment-region)
to:
(define-key global-map [(control c)(\#)] 'py-comment-region)
it works fine.
I went to look where the define the map variable and saw:
(defvar py-shell-map nil
"Keymap used in *Python* shell buffers.")
;; used by py-completion-at-point, the way of python.el
(defvar python-shell-map
(let ((map (copy-keymap comint-mode-map)))
(define-key map [tab] 'py-shell-complete)
(define-key map "\C-c-" 'py-up-exception)
(define-key map "\C-c=" 'py-down-exception)
map)
"Keymap used in *Python* shell buffers.")
Is the 'map' variable defined alright? Should I do some changes to my init file? I assume that this file works to everybody else, so why I need to change the 'map' variable to 'global-map' for it to work in my computer?
I'm running in a virtual machine if that's of any help.
The code you quoted in which you believe map is defined is not actually the relevant portion of the code. It is a different keymap used for a python shell, and it's not the one used when you edit a python file in Emacs.
The line you're editing appears in python-mode inside the following code block:
(defvar python-mode-map)
(setq python-mode-map
(let ((map (make-sparse-keymap)))
;; electric keys
(define-key map [(:)] 'py-electric-colon)
(define-key map [(\#)] 'py-electric-comment)
...
As you can see the variable map is first initialized as a "sparse keymap", then certain key-bindings get defined in that map, and finally the map is set as the value of python-mode-map. The latter is the keymap used in a buffer that is in python-mode.
So the keybindings should work - but of course only in a buffer that is in python-mode. To activate python-mode in a buffer, type M-x python-mode. This works only after the file python-mode.el has been loaded.
You can check if your current buffer is in python-mode in two ways:
your mode line should display the letters "Py"
type M-: ENTER major-mode ENTER -> this should print "python-mode" to the minibuffer
Each major mode and some minor modes have their own keymap, which is overlaid on the global keymap (which is global-map). When you press a key, Emacs tries to find a binding for that key in the overlaid keymaps, falling back to "more global" ones until it gets to the global-map. This is why global-map works and map doesn't.
In lisp, let is used to bind local variables. The map variable doesn't exist outside of the let (or maybe it does, but it's probably not the one you want). Read the documentation for let and defvar (e.g. C-h f defvar).
You need to figure out which keymap is being used in the major mode, and use define-key on that. In this case, (define-key python-mode-map (kbd "C-c #") 'py-comment-region) will probably work. N.B. I do not use python-mode.el, but looking at the source it seems like it uses python-mode-map as the keymap variable. The other keymaps are for auxiliary buffers.