14

I am trying to make the Evil function evil-jump-to-tag, C-] behave like the Emacs binding M-..

The normal behaviour is fine for browsing Tags files, but I want it to work also for Slime's slime-edit-definition, Elisps' elisp-slime-nav-find-elisp-thing-at-point, Clojures cider-jump-to-var, etc...

These major modes and many more have bound some equivalent of jump-to-definition to the keybinding M-..

To get the same behaviour for Evil-mode, do I need to bind a keybinding locally for every of these modes, or is it possible to take a keybinding and tell Emacs, that whenever this key is pressed, use the function bound for that key in Emacs mode?

1

7 Answers 7

10

I've got it working now, thanks to your answers:

(defun my-jump-to-tag ()
  (interactive)
  (evil-emacs-state)
  (call-interactively (key-binding (kbd "M-.")))
  (evil-change-to-previous-state (other-buffer))
  (evil-change-to-previous-state (current-buffer)))

(define-key evil-normal-state-map (kbd "C-]") 'my-jump-to-tag)

This will set evil-state to "Emacs", call the function bound to M-., and change back to the previous emacs state in the other buffer. I've tried it with elisp, slime and go and it's working for all of them.

2
  • 1
    What I use is simpler, and seems to work totally fine: (define-key evil-normal-state-map (kbd "C-]") (kbd "\\ M-.") (where "\" is bound to evil-execute-in-emacs-state).
    – shosti
    Oct 6, 2014 at 5:35
  • @shosti : Yes, this should work at least as well. I've tried it like this, but didn't include the space between the second \ and M.
    – martin
    Oct 6, 2014 at 6:58
3

Try something like

(global-set-key "\C-]" "\M-.")

or, if evil already uses this keybind, you may need to do something like.

(define-key evil-mode-map "\C-]" "\M-.")

This will completely override the behaviour of C-], if you want to keep evil’s behaviour depending on the current major-mode, @Tyler’s solution is more appropriate because you can have a function that decides whether to call M-. or do something esle.

Does this help?

2

I don't understand evil's keymaps, but the following function does whatever M-. is bound to at the moment:

(defun my-tag-jump ()
    (interactive)
    (call-interactively (key-binding (kbd "M-."))))

Binding this to the appropriate evil keymap should do what you want. There may be a more evil-specific way to do this.

evil binds C-] in evil-motion-state-map, so try the following:

(eval-after-load "evil-maps"
    '(define-key evil-motion-state-map "\C-]" 'my-tag-jump))
2
  • I don't see how this could work like this, because evil-mode binds M-. to evil-repeat-pop-next. If you change your function to this: (defun my-tag-jump () (interactive) (evil-emacs-state) (call-interactively (key-binding (kbd "M-."))) (evil-normal-state))
    – martin
    Oct 2, 2014 at 15:21
  • As I said, I don't know evil keymaps. Perhaps the better solution is to find the keymap where evil-jump-to-tag is defined, and rebind it to my function there.
    – Tyler
    Oct 2, 2014 at 16:03
2

In general, it's not possible.

The reason is that there might be several maps that define the same binding, and there's not way to figure out automatically which one you want. (in your example, elisp-slime-nav-mode is such a minor mode). So the only really reliable approach is for you to figure out exactly what definition you want.

That said... there's a possible hack (isn't there always...) Part of what makes it tricky is that the binding that you want to remap is potentially already masked by an evil active keymap, so getting the current binding of M-. is useless.

(defun lookup-no-evil (key)
  ;; excluding evil maps from the lookup. not sure if 
  ;; anything more than evail-normal-state-map is needed
  (let* ((evil-maps (list evil-normal-state-map))
         (bindings
          (remq nil
                (mapcar
                 (lambda (map)
                   (unless (memq map evil-maps)
                     (lookup-key map key)))
                 (current-active-maps)))))
    (when bindings
      ;; let's assume the first one is the right one. 
      ;; Given that minor modes are at the beginning 
      ;; (although this is *not* documented so should not 
      ;; be relied upon), it might be what we would have 
      ;;without evil-mode indeed
      (car bindings))))

(defmacro evil-remap (from to)
  ;; assuming that we want to put it in the normal-state map.
  ;; not sure about that
  `(define-key evil-normal-state-map ,to
       (lambda ()
         (interactive)
         (call-interactively (lookup-no-evil ,from)))))

(evil-remap (kbd "M-.") (kbd "C-]"))

I normally don't use evil at all, so there might be adjustments needed (see embedded comments)

Also, a cleaner approach would be to lookup the bindings once (in a mode hook for example), instead of looking up dynamically every time the keybinding is pressed. But I'm not sure what evil hook to use, so this is left as an exercise ;) (and depending on the order you use for your minor-modes, or if you toggle them dynamically, it might be incorrect)

2

The accepted solution by @severin almost works for me, but, when the tag is not found, the buffer does not return to normal mode. This alternative works for me in all cases:

(defun my-jump-to-tag ()
    (interactive)
    (evil-execute-in-emacs-state)
    (call-interactively (key-binding (kbd "M-."))))
(define-key evil-normal-state-map (kbd "C-]") 'my-jump-to-tag)
2

Some vim-style keybinding functions.

Here are some functions I've got defined to allow vim-style binding in the global map and various evil-states, as well as two general functions that take either an arbitrary key-map or arbitrary binding function. I've put these functions in a gist.

(defun kbd+ (keyrep &optional need-vector)
  (if (vectorp keyrep) keyrep (edmacro-parse-keys keyrep need-vector)))

(defun gmap (keyrep defstr)
  "Vim-style global keybinding. Uses the `global-set-key' binding function."
  (global-set-key (kbd+ keyrep) (edmacro-parse-keys defstr t)))

(defun fmap (keybind-fn keyrep defstr)
  "Vim-style keybinding using the key binding function KEYBIND-FN."
  (call keybind-fn (kbd+ keyrep) (edmacro-parse-keys defstr t)))

(defun xmap (keymap keyrep defstr)
  "Vim-style keybinding in KEYMAP. Uses the `define-key' binding function."
  (define-key keymap (kbd+ keyrep) (edmacro-parse-keys defstr t)))

(defun nmap (keyrep defstr) "Vim-style keybinding for `evil-normal-state.' Uses the `define-key' binding function."
      (xmap evil-normal-state-map keyrep defstr))
(defun imap (keyrep defstr) "Vim-style keybinding for `evil-insert-state'. Uses the `define-key' binding function."
      (xmap evil-insert-state-map keyrep defstr))
(defun vmap (keyrep defstr) "Vim-style keybinding for `evil-visual-state'. Uses the `define-key' binding function."
      (xmap evil-visual-state-map keyrep defstr))
(defun mmap (keyrep defstr) "Vim-style keybinding for `evil-motion-state'. Uses the `define-key' binding function."
      (xmap evil-motion-state-map keyrep defstr))

In general, it's better to only use these functions for keyboard-macro-style bindings (like the use-case in the question), and to use emacs-style keybinding for everything else.

notes

  • The bind-key macro from the use-package package is an excellent and versatile key-binding function.
  • If you want to replace one command with another, you can use Emacs' remapping commands.
  • If you do use these for regular key-bindings, keep in mind that there are no "noremap" versions, so if the bindings in your definition change, so will your user-defined bindings.

Binding C-] to M-..

Note that in normal-state, you'll want to bind to \M-. to access the emacs binding, since normal-state binds M-. to 'evil-repeat-pop-next. So a normal-state binding can be defined with:

(nmap "C-]" "\\ M-.")

or (rebinding any evil-jump-to-tag key in normal-state:

(nmap [remap evil-jump-to-tag] "\\ M-.")
1

I think that the cleanest way is

(define-key evil-normal-state-map (kbd "M-.") 'xref-find-definitions)

(and also add every other map you are interested in)

xref-find-definitions is the function binded to M-. in emacs, as you can see using the command C-h k.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.