It is possible to define ex commands using evil-ex-define-cmd. For example, :quit is defined as:

(evil-ex-define-cmd "q[uit]" 'evil-quit)

I'd like to override this behaviour in particular modes; in particular, when running in git-commit-mode, I'd like for :q or :wq to instead perform a git-commit-commit rather than a regular evil-quit (or evil-save-and-close).

As far as I know, evil does not maintain a mode-local ex command map, and so I need to write a function that calls git-commit-commit if the major mode is git-commit-mode (the easy part); otherwise, delegate to a separate evil function (with arguments forwarded down; the hard part).

The definition would look something like:

(evil-define-command evil-save-and-close-or-commit (<args>)
   (if (eq major-mode "git-commit-mode")
    (evil-save-and-close <args>)))

Any thoughts on how I could achieve this -- in particular, the forwarding of arbitrary arguments to an evil command?


You could achieve this with advice:

(defun my-evil-quit (old-fun &rest args)
  (if (eq major-mode 'lisp-interaction-mode)
    (message "hi!")
    (apply old-fun args)))

(advice-add #'evil-quit :around #'my-evil-quit)

Try this in your *scratch* buffer.


Of course the solution you proposed (along with what @PythonNut provided) does its job for this particular case. However, what if you ever wanted to not just modify behavior of some already existing Ex commands (like you do with advice now), but rather add mode-specific (mode-local) Ex commands (written by you or coming from some package or whatever)? Then clearly, your solution has no go, but here is what can be done instead:

(require 'evil-ex)

(defun evil-ex-define-cmd-local
    (cmd function)
  "Locally binds the function FUNCTION to the command CMD."
  (unless (local-variable-p 'evil-ex-commands)
    (setq-local evil-ex-commands (copy-alist evil-ex-commands)))
  (evil-ex-define-cmd cmd function))

I will continue with the solution to your problem as an example, but, once again, keep in mind that you can use it to define any local Ex command anywhere.

Now, I don't know whether you have tried to update git-commit-mode ever since, but there is no git-commit-mode anymore [1]. Its replacement, git-commit, is now based on with-editor. Thus, what was previously git-commit-commit is now more general with-editor-finish, while what was previously git-commit-abort is now more general with-editor-cancel. As a result, your customization of with-editor could be

(defun with-editor-evil-setup ()
  (evil-ex-define-cmd-local "wq"        #'with-editor-finish)
  (evil-ex-define-cmd-local "bd[elete]" #'with-editor-cancel))

(add-hook 'with-editor-mode-hook #'with-editor-evil-setup)

Basically, evil-ex-define-cmd-local gives you a generic approach to locally customize evil-ex-commands for any buffer (or mode). Hope this helps.

I extensively use the above myself.

For the sake of completeness, let me show you what I have, for example, for git-commit:

(defun git-commit-evil-setup ()
  (evil-ex-define-cmd-local "signoff"   #'git-commit-signoff)
  (evil-ex-define-cmd-local "ack"       #'git-commit-ack)
  (evil-ex-define-cmd-local "test"      #'git-commit-test)
  (evil-ex-define-cmd-local "review"    #'git-commit-review)
  (evil-ex-define-cmd-local "cc"        #'git-commit-cc)
  (evil-ex-define-cmd-local "reported"  #'git-commit-reported)
  (evil-ex-define-cmd-local "suggested" #'git-commit-suggested)
  (evil-ex-define-cmd-local "ms[ave]"   #'git-commit-save-message)
  (evil-ex-define-cmd-local "mn[ext]"   #'git-commit-next-message)
  (evil-ex-define-cmd-local "mp[rev]"   #'git-commit-prev-message)

  (evil-ex-define-cmd-local "c[ommit]"  #'with-editor-finish)
  (evil-ex-define-cmd-local "a[bort]"   #'with-editor-cancel))

(add-hook 'git-commit-setup-hook #'git-commit-evil-setup)
(add-hook 'git-commit-setup-hook #'evil-insert-state)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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