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

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>")

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)

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'.)

Overload a Keybinding in Emacs

I've looked through a number of other questions and el files looking for something i could modify to suit my needs but I'm having trouble so I came to the experts.
Is there anyway to have a key behave differently depending on where in the line the cursor is?
To be more specific I'd like to map the tab key to go to the end of the line if I'm in the middle of the line but work as a tab normally would if my cursor is positioned at the beginning of the line.
So far I have braces and quotes auto-pairing and re-positioning the cursor within them for C++/Java etc. I'd like to use the tab key to end-of-line if for example a function doesn't have any arguments.
Behaving differently depending on where point is in the line is the easy bit (see (if (looking-back "^") ...) in the code). "[Working] as a tab normally would" is the harder bit, as that's contextual.
Here's one approach, but I was thinking afterwards that a more robust method would be to define a minor mode with its own binding for TAB and let that function look up the fallback binding dynamically. I wasn't sure how to do that last bit, but there's a solution right here:
Emacs key binding fallback
(defvar my-major-mode-tab-function-alist nil)
(defmacro make-my-tab-function ()
"Return a major mode-specific function suitable for binding to TAB.
Performs the original TAB behaviour when point is at the beginning of
a line, and moves point to the end of the line otherwise."
;; If we have already defined a custom function for this mode,
;; return that (otherwise that would be our fall-back function).
(or (cdr (assq major-mode my-major-mode-tab-function-alist))
;; Otherwise find the current binding for this mode, and
;; specify it as the fall-back for our custom function.
(let ((original-tab-function (key-binding (kbd "TAB") t)))
`(let ((new-tab-function
(lambda ()
(interactive)
(if (looking-back "^") ;; point is at bol
(,original-tab-function)
(move-end-of-line nil)))))
(add-to-list 'my-major-mode-tab-function-alist
(cons ',major-mode new-tab-function))
new-tab-function))))
(add-hook
'java-mode-hook
(lambda () (local-set-key (kbd "TAB") (make-my-tab-function)))
t) ;; Append, so that we run after the other hooks.
This page of Emacs Wiki lists several packages (smarttab, etc.) which make TAB do different things depending on the context. You can probably modify one of them to do what you want.

Globally override key binding in Emacs

How can I set a key binding that globally overrides and takes precedence over all other bindings for that key? I want to override all major/minor mode maps and make sure my binding is always in effect.
This of course doesn't work:
(global-set-key "\C-i" 'some-function)
It works in text-mode, but when I use lisp-mode, C-i is rebound to lisp-indent-line.
I can go through and override this binding in lisp-mode and in every other mode individually, but there must be an easier way. Every time I install a new mode for a new file type, I'd have to go back and check to make sure that all of my key bindings aren't being overridden by the new mode.
I want to do this because I want to emulate bindings I've already learned and ingrained from other editors.
I use a minor mode for all my "override" key bindings:
(defvar my-keys-minor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-i") 'some-function)
map)
"my-keys-minor-mode keymap.")
(define-minor-mode my-keys-minor-mode
"A minor mode so that my key settings override annoying major modes."
:init-value t
:lighter " my-keys")
(my-keys-minor-mode 1)
This has the added benefit of being able to turn off all my modifications in one fell swoop (just disable the minor mode) in case someone else is driving the keyboard or if I need to see what a default key binding does.
Note that you may need to turn this off in the minibuffer:
(defun my-minibuffer-setup-hook ()
(my-keys-minor-mode 0))
(add-hook 'minibuffer-setup-hook 'my-minibuffer-setup-hook)
As an addition to scottfrazer's answer, I've written the following so that my keybindings retain precedence, even if subsequently-loaded libraries bring in new keymaps of their own.
Because keymaps can be generated at compile time, load seemed like the best place to do this.
(add-hook 'after-load-functions 'my-keys-have-priority)
(defun my-keys-have-priority (_file)
"Try to ensure that my keybindings retain priority over other minor modes.
Called via the `after-load-functions' special hook."
(unless (eq (caar minor-mode-map-alist) 'my-keys-minor-mode)
(let ((mykeys (assq 'my-keys-minor-mode minor-mode-map-alist)))
(assq-delete-all 'my-keys-minor-mode minor-mode-map-alist)
(add-to-list 'minor-mode-map-alist mykeys))))
Install use-package, eval and you're done:
(require 'bind-key)
(bind-key* "C-i" 'some-function)
I found this question while searching for "emacs undefine org mode keybindings", because I wanted to unbind the existing C-c C-b behavior to allow my global map to bury-buffer to work in an org buffer.
This ended up being the simplest solution for me:
(add-hook 'org-mode-hook
(lambda ()
(local-unset-key (kbd "C-c C-b"))))
Although scottfrazer's answer is exactly what you asked for, I will mention for posterity another solution.
From The Emacs Manual:
"Don't define C-c letter as a key in Lisp programs. Sequences consisting of C-c and a letter (either upper or lower case) are reserved for users; they are the only sequences reserved for users, so do not block them."
If you bind your personal global bindings to C-c plus a letter, then you "should" be safe. However, this is merely a convention, and any mode is still able to override your bindings.
If you want to "always use the keybinds in the map, unless I explicitly override them for a specific mode-map", and assuming you are using scottfrazier's approach, you want:
(defun locally-override (key cmd)
(unless (local-variable-p 'my-keys-minor-mode-map)
(set (make-variable-buffer-local 'my-keys-minor-mode-map)
(make-sparse-keymap))
(set-keymap-parent my-keys-minor-mode-map
(default-value 'my-keys-minor-mode-map)))
(define-key my-keys-minor-mode-map key cmd))
So
(locally-override "\C-i" nil)
should remove the "\C-i" binding from the minor mode in the current buffer only. Warning: this is completely untested, but seems like the right approach. The point of setting the parent rather than just coping the global value of my-keys-minor-mode-map is so any later changes to the global value are automatically reflected in the local value.
I don't think you can. That is roughly equivalent to saying that you want to define a global variable that cannot be hidden by local variable declarations in functions. Scope just doesn't work that way.
However, there might be a way to write an elisp function to go through the mode list and reassign it in every single one for you.
Unless you really want to do this yourself, you should check around and see if anyone else already has done it.
There is a package for Emacs which gives your windows-like keybindings. You should be able to find it through google.