1083 lines
42 KiB
EmacsLisp
1083 lines
42 KiB
EmacsLisp
|
;;; evil-core.el --- Core functionality
|
||
|
;; Author: Vegard Øye <vegard_oye at hotmail.com>
|
||
|
;; Maintainer: Vegard Øye <vegard_oye at hotmail.com>
|
||
|
|
||
|
;; Version: 1.0.1
|
||
|
|
||
|
;;
|
||
|
;; This file is NOT part of GNU Emacs.
|
||
|
|
||
|
;;; License:
|
||
|
|
||
|
;; This file is part of Evil.
|
||
|
;;
|
||
|
;; Evil is free software: you can redistribute it and/or modify
|
||
|
;; it under the terms of the GNU General Public License as published by
|
||
|
;; the Free Software Foundation, either version 3 of the License, or
|
||
|
;; (at your option) any later version.
|
||
|
;;
|
||
|
;; Evil is distributed in the hope that it will be useful,
|
||
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
;; GNU General Public License for more details.
|
||
|
;;
|
||
|
;; You should have received a copy of the GNU General Public License
|
||
|
;; along with Evil. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
;;; Commentary:
|
||
|
|
||
|
;; Evil is defined as a globalized minor mode, enabled with the toggle
|
||
|
;; function `evil-mode'. This in turn enables `evil-local-mode' in
|
||
|
;; every buffer, which sets up the buffer's state.
|
||
|
;;
|
||
|
;; Each state has its own keymaps, and these keymaps have status as
|
||
|
;; "emulation keymaps" with priority over regular keymaps. Emacs
|
||
|
;; maintains the following keymap hierarchy (highest priority first):
|
||
|
;;
|
||
|
;; * Overriding keymaps/overlay keymaps...
|
||
|
;; * Emulation mode keymaps...
|
||
|
;; - Evil keymaps...
|
||
|
;; * Minor mode keymaps...
|
||
|
;; * Local keymap (`local-set-key')
|
||
|
;; * Global keymap (`global-set-key')
|
||
|
;;
|
||
|
;; Within this hierarchy, Evil arranges the keymaps for the current
|
||
|
;; state as shown below:
|
||
|
;;
|
||
|
;; * Intercept keymaps...
|
||
|
;; * Local state keymap
|
||
|
;; * Auxiliary keymaps...
|
||
|
;; * Overriding keymaps...
|
||
|
;; * Global state keymap
|
||
|
;; * Keymaps for other states...
|
||
|
;;
|
||
|
;; These keymaps are listed in `evil-mode-map-alist', which is listed
|
||
|
;; in `emulation-mode-map-alist'.
|
||
|
;;
|
||
|
;; Most of the key bindings for a state are stored in its global
|
||
|
;; keymap, which has a name such as `evil-normal-state-map'. (See the
|
||
|
;; file evil-maps.el, which contains all the default key bindings.)
|
||
|
;; A state also has a local keymap (`evil-normal-state-local-map'),
|
||
|
;; which may contain user customizations for the current buffer.
|
||
|
;; Furthermore, any Emacs mode may be assigned state bindings of its
|
||
|
;; own by passing the mode's keymap to the function `evil-define-key'.
|
||
|
;; These mode-specific bindings are ultimately stored in so-called
|
||
|
;; auxiliary keymaps, which are sandwiched between the local keymap
|
||
|
;; and the global keymap. Finally, the state may also activate the
|
||
|
;; keymaps of other states (e.g., Normal state inherits bindings
|
||
|
;; from Motion state).
|
||
|
;;
|
||
|
;; For integration purposes, a regular Emacs keymap may be "elevated"
|
||
|
;; to emulation status by passing it to `evil-make-intercept-map' or
|
||
|
;; `evil-make-overriding-map'. An "intercept" keymap has priority over
|
||
|
;; all other Evil keymaps. (Evil uses this facility when debugging and
|
||
|
;; for handling the "ESC" key in the terminal.) More common is the
|
||
|
;; "overriding" keymap, which only has priority over the global state
|
||
|
;; keymap. (This is useful for adapting key-heavy modes such as Dired,
|
||
|
;; where all but a few keys should be left as-is and should not be
|
||
|
;; shadowed by Evil's default bindings.)
|
||
|
;;
|
||
|
;; States are defined with the macro `evil-define-state', which
|
||
|
;; creates a command for switching to the state. This command,
|
||
|
;; for example `evil-normal-state' for Normal state, performs
|
||
|
;; the following tasks:
|
||
|
;;
|
||
|
;; * Setting `evil-state' to the new state.
|
||
|
;; * Refreshing the keymaps in `evil-mode-map-alist'.
|
||
|
;; * Updating the mode line.
|
||
|
;; - Normal state depends on `evil-normal-state-tag'.
|
||
|
;; * Adjusting the cursor's appearance.
|
||
|
;; - Normal state depends on `evil-normal-state-cursor'.
|
||
|
;; * Displaying a message in the echo area.
|
||
|
;; - Normal state depends on `evil-normal-state-message'.
|
||
|
;; * Running hooks.
|
||
|
;; - Normal state runs `evil-normal-state-entry-hook' when
|
||
|
;; entering, and `evil-normal-state-exit-hook' when exiting.
|
||
|
;;
|
||
|
;; The various properties of a state can be accessed through their
|
||
|
;; respective variables, or by passing a keyword and the state's name
|
||
|
;; to the `evil-state-property' function. Evil defines the states
|
||
|
;; Normal state ("normal"), Insert state ("insert"), Visual state
|
||
|
;; ("visual"), Replace state ("replace"), Operator-Pending state
|
||
|
;; ("operator"), Motion state ("motion") and Emacs state ("emacs").
|
||
|
|
||
|
(require 'evil-common)
|
||
|
|
||
|
;;; Code:
|
||
|
|
||
|
(define-minor-mode evil-local-mode
|
||
|
"Minor mode for setting up Evil in a single buffer."
|
||
|
:init-value nil
|
||
|
(cond
|
||
|
((evil-disabled-buffer-p))
|
||
|
(evil-local-mode
|
||
|
(setq emulation-mode-map-alists
|
||
|
(evil-concat-lists '(evil-mode-map-alist)
|
||
|
emulation-mode-map-alists))
|
||
|
(evil-initialize-local-keymaps)
|
||
|
;; restore the proper value of `major-mode' in Fundamental buffers
|
||
|
(when (eq major-mode 'turn-on-evil-mode)
|
||
|
(setq major-mode 'fundamental-mode))
|
||
|
;; The initial state is usually setup by `evil-initialize' when
|
||
|
;; the major-mode in a buffer changes. This preliminary
|
||
|
;; initialization is only for the case when `evil-local-mode' is
|
||
|
;; called directly for the first time in a buffer.
|
||
|
(unless evil-state (evil-initialize-state))
|
||
|
(add-hook 'input-method-activate-hook #'evil-activate-input-method t t)
|
||
|
(add-hook 'input-method-deactivate-hook #'evil-deactivate-input-method t t)
|
||
|
(add-hook 'activate-mark-hook #'evil-visual-activate-hook nil t)
|
||
|
(add-hook 'pre-command-hook #'evil-repeat-pre-hook)
|
||
|
(add-hook 'pre-command-hook #'evil-jump-hook nil t)
|
||
|
(add-hook 'post-command-hook #'evil-repeat-post-hook)
|
||
|
(add-hook 'post-command-hook #'evil-refresh-cursor))
|
||
|
(t
|
||
|
(evil-refresh-mode-line)
|
||
|
(remove-hook 'pre-command-hook #'evil-jump-hook t)
|
||
|
(remove-hook 'activate-mark-hook #'evil-visual-activate-hook t)
|
||
|
(remove-hook 'input-method-activate-hook #'evil-activate-input-method t)
|
||
|
(remove-hook 'input-method-deactivate-hook #'evil-deactivate-input-method t)
|
||
|
(evil-change-state nil))))
|
||
|
|
||
|
(defun turn-on-evil-mode (&optional arg)
|
||
|
"Turn on Evil in the current buffer."
|
||
|
(interactive)
|
||
|
(evil-local-mode (or arg 1)))
|
||
|
|
||
|
(defun turn-off-evil-mode (&optional arg)
|
||
|
"Turn off Evil in the current buffer."
|
||
|
(interactive)
|
||
|
(evil-local-mode (or arg -1)))
|
||
|
|
||
|
;; The function `evil-initialize' should only be used to initialize
|
||
|
;; `evil-local-mode' from the globalized minor-mode `evil-mode'. It is
|
||
|
;; called whenever evil is enabled in a buffer for the first time or
|
||
|
;; when evil is active and the major-mode of the buffer changes. In
|
||
|
;; addition to enabling `evil-local-mode' it also sets the initial
|
||
|
;; evil-state according to the major-mode.
|
||
|
(defun evil-initialize ()
|
||
|
"Enable Evil in the current buffer, if appropriate.
|
||
|
To enable Evil globally, do (evil-mode 1)."
|
||
|
;; TODO: option for enabling vi keys in the minibuffer
|
||
|
(unless (minibufferp)
|
||
|
(evil-local-mode 1)
|
||
|
(evil-initialize-state)))
|
||
|
|
||
|
;;;###autoload (autoload 'evil-mode "evil" "Toggle evil in all buffers" t)
|
||
|
(define-globalized-minor-mode evil-mode
|
||
|
evil-local-mode evil-initialize)
|
||
|
|
||
|
;; No hooks are run in Fundamental buffers, so other measures are
|
||
|
;; necessary to initialize Evil in these buffers. When Evil is
|
||
|
;; enabled globally, the default value of `major-mode' is set to
|
||
|
;; `turn-on-evil-mode', so that Evil is enabled in Fundamental
|
||
|
;; buffers as well. Then, the buffer-local value of `major-mode' is
|
||
|
;; changed back to `fundamental-mode'. (Since the `evil-mode' function
|
||
|
;; is created by a macro, we use `defadvice' to augment it.)
|
||
|
(defadvice evil-mode (after start-evil activate)
|
||
|
"Enable Evil in Fundamental mode."
|
||
|
(if evil-mode
|
||
|
(progn
|
||
|
;; changed back by `evil-local-mode'
|
||
|
(setq-default major-mode 'turn-on-evil-mode)
|
||
|
(ad-enable-regexp "^evil")
|
||
|
(ad-activate-regexp "^evil"))
|
||
|
(setq-default major-mode 'fundamental-mode)
|
||
|
(ad-disable-regexp "^evil")
|
||
|
(ad-update-regexp "^evil")))
|
||
|
|
||
|
(put 'evil-mode 'function-documentation
|
||
|
"Toggle Evil in all buffers.
|
||
|
Enable with positive ARG and disable with negative ARG.
|
||
|
See `evil-local-mode' to toggle Evil in the
|
||
|
current buffer only.")
|
||
|
|
||
|
(defun evil-change-state (state &optional message)
|
||
|
"Change the state to STATE.
|
||
|
If STATE is nil, disable all states."
|
||
|
(let ((func (evil-state-property (or state evil-state) :toggle)))
|
||
|
(when (and (functionp func)
|
||
|
(or message (not (eq state evil-state))))
|
||
|
(funcall func (if state (and message 1) -1)))))
|
||
|
|
||
|
(defmacro evil-save-state (&rest body)
|
||
|
"Save the current state; execute BODY; restore the state."
|
||
|
(declare (indent defun)
|
||
|
(debug t))
|
||
|
`(let* ((evil-state evil-state)
|
||
|
(evil-previous-state evil-previous-state)
|
||
|
(evil-previous-state-alist (copy-tree evil-previous-state-alist))
|
||
|
(evil-next-state evil-next-state)
|
||
|
(old-state evil-state)
|
||
|
(inhibit-quit t))
|
||
|
(unwind-protect
|
||
|
(progn ,@body)
|
||
|
(evil-change-state old-state))))
|
||
|
|
||
|
(defmacro evil-with-state (state &rest body)
|
||
|
"Change to STATE and execute BODY without refreshing the display.
|
||
|
Restore the previous state afterwards."
|
||
|
(declare (indent defun)
|
||
|
(debug t))
|
||
|
`(evil-without-display
|
||
|
(evil-save-state
|
||
|
(evil-change-state ',state)
|
||
|
,@body)))
|
||
|
|
||
|
(defun evil-initializing-p (&optional buffer)
|
||
|
"Whether Evil is in the process of being initialized."
|
||
|
(memq (or buffer (current-buffer)) evil-mode-buffers))
|
||
|
|
||
|
(defun evil-initialize-state (&optional state buffer)
|
||
|
"Set up the initial state for BUFFER.
|
||
|
BUFFER defaults to the current buffer.
|
||
|
Uses STATE if specified, or calls `evil-initial-state-for-buffer'.
|
||
|
See also `evil-set-initial-state'."
|
||
|
(with-current-buffer (or buffer (current-buffer))
|
||
|
(if state (evil-change-state state)
|
||
|
(evil-change-to-initial-state buffer))))
|
||
|
(put 'evil-initialize-state 'permanent-local-hook t)
|
||
|
|
||
|
(defun evil-initial-state-for-buffer-name (&optional name default)
|
||
|
"Return the initial Evil state to use for a buffer with name NAME.
|
||
|
Matches the name against the regular expressions in
|
||
|
`evil-buffer-regexps'. If none matches, returns DEFAULT."
|
||
|
(let ((name (if (stringp name) name (buffer-name name)))
|
||
|
regexp state)
|
||
|
(when (stringp name)
|
||
|
(catch 'done
|
||
|
(dolist (entry evil-buffer-regexps default)
|
||
|
(setq regexp (car entry)
|
||
|
state (cdr entry))
|
||
|
(when (string-match regexp name)
|
||
|
(throw 'done state)))))))
|
||
|
|
||
|
(defun evil-disabled-buffer-p (&optional buffer)
|
||
|
"Whether Evil should be disabled in BUFFER."
|
||
|
(null (evil-initial-state-for-buffer-name buffer 'undefined)))
|
||
|
|
||
|
(defun evil-initial-state-for-buffer (&optional buffer default)
|
||
|
"Return the initial Evil state to use for BUFFER.
|
||
|
BUFFER defaults to the current buffer. Returns DEFAULT
|
||
|
if no initial state is associated with BUFFER.
|
||
|
See also `evil-initial-state'."
|
||
|
(with-current-buffer (or buffer (current-buffer))
|
||
|
(or (evil-initial-state-for-buffer-name (buffer-name))
|
||
|
(catch 'done
|
||
|
(dolist (mode minor-mode-map-alist)
|
||
|
(setq mode (car-safe mode))
|
||
|
(when (and (boundp mode) (symbol-value mode))
|
||
|
(when (setq mode (evil-initial-state mode))
|
||
|
(throw 'done mode)))))
|
||
|
(evil-initial-state major-mode)
|
||
|
default)))
|
||
|
|
||
|
(defun evil-initial-state (mode &optional default)
|
||
|
"Return the Evil state to use for MODE.
|
||
|
Returns DEFAULT if no initial state is associated with MODE.
|
||
|
The initial state for a mode can be set with
|
||
|
`evil-set-initial-state'."
|
||
|
(let (state modes)
|
||
|
(catch 'done
|
||
|
(dolist (entry (evil-state-property t :modes) default)
|
||
|
(setq state (car entry)
|
||
|
modes (symbol-value (cdr entry)))
|
||
|
(when (memq mode modes)
|
||
|
(throw 'done state))))))
|
||
|
|
||
|
(defun evil-set-initial-state (mode state)
|
||
|
"Set the initial state for MODE to STATE.
|
||
|
This is the state the buffer comes up in."
|
||
|
(dolist (modes (evil-state-property t :modes))
|
||
|
(setq modes (cdr-safe modes))
|
||
|
(set modes (delq mode (symbol-value modes))))
|
||
|
(when state
|
||
|
(add-to-list (evil-state-property state :modes) mode)))
|
||
|
|
||
|
(evil-define-command evil-change-to-initial-state
|
||
|
(&optional buffer message)
|
||
|
"Change the state of BUFFER to its initial state.
|
||
|
This is the state the buffer came up in."
|
||
|
:keep-visual t
|
||
|
:suppress-operator t
|
||
|
(with-current-buffer (or buffer (current-buffer))
|
||
|
(evil-change-state (evil-initial-state-for-buffer
|
||
|
buffer (or evil-default-state 'normal))
|
||
|
message)))
|
||
|
|
||
|
(evil-define-command evil-change-to-previous-state
|
||
|
(&optional buffer message)
|
||
|
"Change the state of BUFFER to its previous state."
|
||
|
:keep-visual t
|
||
|
:repeat abort
|
||
|
:suppress-operator t
|
||
|
(with-current-buffer (or buffer (current-buffer))
|
||
|
(let ((prev-state evil-previous-state)
|
||
|
(prev-prev-state (cdr-safe (assoc evil-previous-state
|
||
|
evil-previous-state-alist))))
|
||
|
(evil-change-state nil)
|
||
|
(when prev-prev-state
|
||
|
(setq evil-previous-state prev-prev-state))
|
||
|
(evil-change-state (or prev-state evil-default-state 'normal)
|
||
|
message))))
|
||
|
|
||
|
;; When a buffer is created in a low-level way, it is invisible to
|
||
|
;; Evil (as well as other globalized minor modes) because no hooks are
|
||
|
;; run. This is appropriate since many buffers are used for throwaway
|
||
|
;; purposes. Passing the buffer to `display-buffer' indicates
|
||
|
;; otherwise, though, so advise this function to initialize Evil.
|
||
|
(defadvice display-buffer (before evil activate)
|
||
|
"Initialize Evil in the displayed buffer."
|
||
|
(when evil-mode
|
||
|
(when (get-buffer (ad-get-arg 0))
|
||
|
(with-current-buffer (ad-get-arg 0)
|
||
|
(unless evil-local-mode
|
||
|
(evil-local-mode 1))))))
|
||
|
|
||
|
(defadvice switch-to-buffer (before evil activate)
|
||
|
"Initialize Evil in the displayed buffer."
|
||
|
(when evil-mode
|
||
|
(let* ((arg0 (ad-get-arg 0))
|
||
|
(buffer (if arg0 (get-buffer arg0) (other-buffer))))
|
||
|
(when buffer
|
||
|
(with-current-buffer buffer
|
||
|
(unless evil-local-mode
|
||
|
(evil-local-mode 1)))))))
|
||
|
|
||
|
(defun evil-generate-mode-line-tag (&optional state)
|
||
|
"Generate the evil mode-line tag for STATE."
|
||
|
(let ((tag (evil-state-property state :tag t)))
|
||
|
;; prepare mode-line: add tooltip
|
||
|
(if (stringp tag)
|
||
|
(propertize tag
|
||
|
'help-echo (evil-state-property state :name)
|
||
|
'mouse-face 'mode-line-highlight)
|
||
|
tag)))
|
||
|
|
||
|
(defun evil-refresh-mode-line (&optional state)
|
||
|
"Refresh mode line tag."
|
||
|
(when (listp mode-line-format)
|
||
|
(setq evil-mode-line-tag (evil-generate-mode-line-tag state))
|
||
|
;; refresh mode line data structure
|
||
|
;; first remove evil from mode-line
|
||
|
(setq mode-line-format (delq 'evil-mode-line-tag mode-line-format))
|
||
|
(let ((mlpos mode-line-format)
|
||
|
pred which where)
|
||
|
;; determine before/after which symbol the tag should be placed
|
||
|
(cond
|
||
|
((eq evil-mode-line-format 'before)
|
||
|
(setq where 'after which 'mode-line-position))
|
||
|
((eq evil-mode-line-format 'after)
|
||
|
(setq where 'after which 'mode-line-modes))
|
||
|
((consp evil-mode-line-format)
|
||
|
(setq where (car evil-mode-line-format)
|
||
|
which (cdr evil-mode-line-format))))
|
||
|
;; find the cons-cell of the symbol before/after which the tag
|
||
|
;; should be placed
|
||
|
(while (and mlpos
|
||
|
(let ((sym (or (car-safe (car mlpos)) (car mlpos))))
|
||
|
(not (eq which sym))))
|
||
|
(setq pred mlpos
|
||
|
mlpos (cdr mlpos)))
|
||
|
;; put evil tag at the right position in the mode line
|
||
|
(cond
|
||
|
((not mlpos)) ;; position not found, so do not add the tag
|
||
|
((eq where 'before)
|
||
|
(if pred
|
||
|
(setcdr pred (cons 'evil-mode-line-tag mlpos))
|
||
|
(setq mode-line-format
|
||
|
(cons 'evil-mode-line-tag mode-line-format))))
|
||
|
((eq where 'after)
|
||
|
(setcdr mlpos (cons 'evil-mode-line-tag (cdr mlpos)))))
|
||
|
(force-mode-line-update))))
|
||
|
|
||
|
;; input methods should be disabled in non-insertion states
|
||
|
(defun evil-activate-input-method ()
|
||
|
"Disable input method in states with :input-method nil."
|
||
|
(let (input-method-activate-hook
|
||
|
input-method-deactivate-hook)
|
||
|
(when (and evil-local-mode evil-state)
|
||
|
(setq evil-input-method current-input-method)
|
||
|
(unless (evil-state-property evil-state :input-method)
|
||
|
(deactivate-input-method)))))
|
||
|
(put 'evil-activate-input-method 'permanent-local-hook t)
|
||
|
|
||
|
(defun evil-deactivate-input-method ()
|
||
|
"Disable input method in states with :input-method nil."
|
||
|
(let (input-method-activate-hook
|
||
|
input-method-deactivate-hook)
|
||
|
(when (and evil-local-mode evil-state)
|
||
|
(setq evil-input-method nil))))
|
||
|
(put 'evil-deactivate-input-method 'permanent-local-hook t)
|
||
|
|
||
|
(defadvice toggle-input-method (around evil activate)
|
||
|
"Refresh `evil-input-method'."
|
||
|
(cond
|
||
|
((not evil-local-mode)
|
||
|
ad-do-it)
|
||
|
((evil-state-property evil-state :input-method)
|
||
|
ad-do-it)
|
||
|
(t
|
||
|
(let ((current-input-method evil-input-method))
|
||
|
ad-do-it))))
|
||
|
|
||
|
;; Local keymaps are implemented using buffer-local variables.
|
||
|
;; However, unless a buffer-local value already exists,
|
||
|
;; `define-key' acts on the variable's default (global) value.
|
||
|
;; So we need to initialize the variable whenever we enter a
|
||
|
;; new buffer or when the buffer-local values are reset.
|
||
|
(defun evil-initialize-local-keymaps ()
|
||
|
"Initialize a buffer-local value for local keymaps as necessary.
|
||
|
The initial value is that of `make-sparse-keymap'."
|
||
|
(dolist (entry evil-local-keymaps-alist)
|
||
|
(let ((mode (car entry))
|
||
|
(map (cdr entry)))
|
||
|
(unless (and (keymapp (symbol-value map))
|
||
|
(assq map (buffer-local-variables)))
|
||
|
(set map (make-sparse-keymap))))))
|
||
|
|
||
|
(defun evil-make-overriding-map (keymap &optional state copy)
|
||
|
"Give KEYMAP precedence over the global keymap of STATE.
|
||
|
The keymap will have lower precedence than custom STATE bindings.
|
||
|
If STATE is nil, give it precedence over all states.
|
||
|
If COPY is t, create a copy of KEYMAP and give that
|
||
|
higher precedence. See also `evil-make-intercept-map'."
|
||
|
(let ((key [override-state]))
|
||
|
(if (not copy)
|
||
|
(define-key keymap key (or state 'all))
|
||
|
(unless (keymapp copy)
|
||
|
(setq copy (assq-delete-all 'menu-bar (copy-keymap keymap))))
|
||
|
(define-key copy key (or state 'all))
|
||
|
(define-key keymap key copy))))
|
||
|
|
||
|
(defun evil-make-intercept-map (keymap &optional state)
|
||
|
"Give KEYMAP precedence over all Evil keymaps in STATE.
|
||
|
If STATE is nil, give it precedence over all states.
|
||
|
See also `evil-make-overriding-map'."
|
||
|
(let ((key [intercept-state]))
|
||
|
(define-key keymap key (or state 'all))))
|
||
|
|
||
|
(defmacro evil-define-keymap (keymap doc &rest body)
|
||
|
"Define a keymap KEYMAP listed in `evil-mode-map-alist'.
|
||
|
That means it will have precedence over regular keymaps.
|
||
|
|
||
|
DOC is the documentation for the variable. BODY, if specified,
|
||
|
is executed after toggling the mode. Optional keyword arguments
|
||
|
may be specified before the body code:
|
||
|
|
||
|
:mode VAR Mode variable. If unspecified, the variable
|
||
|
is based on the keymap name.
|
||
|
:local BOOLEAN Whether the keymap should be buffer-local, that is,
|
||
|
reinitialized for each buffer.
|
||
|
:func BOOLEAN Create a toggle function even if BODY is empty.
|
||
|
|
||
|
\(fn KEYMAP DOC [[KEY VAL]...] BODY...)"
|
||
|
(declare (indent defun)
|
||
|
(debug (&define name
|
||
|
[&optional stringp]
|
||
|
[&rest [keywordp sexp]]
|
||
|
def-body)))
|
||
|
(let ((func t)
|
||
|
arg intercept key local mode overriding)
|
||
|
(while (keywordp (car-safe body))
|
||
|
(setq key (pop body)
|
||
|
arg (pop body))
|
||
|
(cond
|
||
|
((eq key :mode)
|
||
|
(setq mode arg))
|
||
|
((eq key :local)
|
||
|
(setq local arg))
|
||
|
((eq key :func)
|
||
|
(setq func arg))
|
||
|
((eq key :intercept)
|
||
|
(setq intercept arg))
|
||
|
((eq key :overriding)
|
||
|
(setq overriding arg))))
|
||
|
(setq mode (or mode
|
||
|
(intern (replace-regexp-in-string
|
||
|
"\\(?:-\\(?:mode-\\)?\\(?:key\\)?map\\)?$"
|
||
|
"-mode"
|
||
|
(symbol-name keymap)))))
|
||
|
`(progn
|
||
|
(defvar ,keymap ,(unless local '(make-sparse-keymap)))
|
||
|
(unless (get ',keymap 'variable-documentation)
|
||
|
(put ',keymap 'variable-documentation ,doc))
|
||
|
(defvar ,mode nil)
|
||
|
(unless (get ',mode 'variable-documentation)
|
||
|
(put ',mode 'variable-documentation ,doc))
|
||
|
(make-variable-buffer-local ',mode)
|
||
|
(put ',mode 'permanent-local t)
|
||
|
(when ,intercept
|
||
|
(evil-make-intercept-map ,keymap))
|
||
|
(when ,overriding
|
||
|
(evil-make-overriding-map ,keymap))
|
||
|
,@(if local
|
||
|
`((make-variable-buffer-local ',keymap)
|
||
|
(put ',keymap 'permanent-local t)
|
||
|
(evil-add-to-alist 'evil-local-keymaps-alist
|
||
|
',mode ',keymap))
|
||
|
`((evil-add-to-alist 'evil-global-keymaps-alist
|
||
|
',mode ',keymap)
|
||
|
(evil-add-to-alist 'evil-mode-map-alist
|
||
|
',mode ,keymap)))
|
||
|
,(when (or body func)
|
||
|
`(defun ,mode (&optional arg)
|
||
|
,@(when doc `(,doc))
|
||
|
(interactive)
|
||
|
(cond
|
||
|
((numberp arg)
|
||
|
(setq ,mode (> arg 0)))
|
||
|
(t
|
||
|
(setq ,mode (not ,mode))))
|
||
|
,@body))
|
||
|
',keymap)))
|
||
|
|
||
|
;; Intercept the ESC event when running in the terminal. This allows
|
||
|
;; keys that use "ESC" as a prefix key, such as "M-x". If "ESC" is
|
||
|
;; immediately followed by another key, or another key is pressed
|
||
|
;; within `evil-esc-delay', the prefixed key sequence is sent.
|
||
|
;; Otherwise only [escape] is sent.
|
||
|
(evil-define-keymap evil-esc-map
|
||
|
"Keymap for intercepting ESC."
|
||
|
:intercept t)
|
||
|
|
||
|
(defun evil-turn-on-esc-mode ()
|
||
|
"Enable interception of ESC."
|
||
|
(unless (eq this-command #'evil-esc)
|
||
|
(evil-esc-mode 1)
|
||
|
(remove-hook 'pre-command-hook #'evil-turn-on-esc-mode t)))
|
||
|
(put 'evil-turn-on-esc-mode 'permanent-local-hook t)
|
||
|
|
||
|
;; `evil-esc' is bound to (kbd "ESC"), while other commands
|
||
|
;; are bound to [escape]. That way `evil-esc' is used only when
|
||
|
;; (kbd "ESC") and [escape] are the same event -- i.e., when
|
||
|
;; running Emacs in the terminal.
|
||
|
(define-key evil-esc-map (kbd "ESC") 'evil-esc)
|
||
|
|
||
|
(defun evil-state-p (sym)
|
||
|
"Whether SYM is the name of a state."
|
||
|
(assq sym evil-state-properties))
|
||
|
|
||
|
(defun evil-state-keymaps (state &rest excluded)
|
||
|
"Return a keymap alist of keymaps activated by STATE.
|
||
|
If STATE references other states in its :enable property,
|
||
|
these states are recursively processed and added to the list.
|
||
|
\(The EXCLUDED argument is an internal safeguard against
|
||
|
infinite recursion, keeping track of processed states.)"
|
||
|
(let* ((state (or state evil-state))
|
||
|
(enable (evil-state-property state :enable))
|
||
|
(map (cons
|
||
|
(evil-state-property state :mode)
|
||
|
(evil-state-property state :keymap t)))
|
||
|
(local-map (cons
|
||
|
(evil-state-property state :local)
|
||
|
(evil-state-property state :local-keymap t)))
|
||
|
(aux-maps (evil-state-auxiliary-keymaps state))
|
||
|
(overriding-maps
|
||
|
(evil-state-overriding-keymaps state))
|
||
|
(intercept-maps
|
||
|
(evil-state-intercept-keymaps state))
|
||
|
(result `(,intercept-maps))
|
||
|
(remove-duplicates (null excluded)))
|
||
|
(unless (memq state enable)
|
||
|
(setq enable (cons state enable)))
|
||
|
;; process STATE's :enable property
|
||
|
(dolist (entry enable)
|
||
|
(cond
|
||
|
((memq entry excluded))
|
||
|
;; the keymaps for STATE
|
||
|
((eq entry state)
|
||
|
(setq result `(,@result
|
||
|
(,local-map)
|
||
|
,aux-maps
|
||
|
,overriding-maps
|
||
|
(,map)))
|
||
|
(push state excluded))
|
||
|
;; the keymaps for another state: call `evil-state-keymaps'
|
||
|
;; recursively, but keep track of processed states
|
||
|
((evil-state-p entry)
|
||
|
(setq result `(,@result
|
||
|
,(apply #'evil-state-keymaps entry excluded))))
|
||
|
;; a single keymap
|
||
|
((or (keymapp entry)
|
||
|
(and (keymapp (symbol-value entry))
|
||
|
(setq entry (symbol-value entry)))
|
||
|
(setq entry (evil-keymap-for-mode entry)))
|
||
|
(setq result `(,@result
|
||
|
((,(evil-mode-for-keymap entry t) .
|
||
|
,entry)))))))
|
||
|
;; postpone the expensive filtering of duplicates to the top level
|
||
|
(if remove-duplicates
|
||
|
(apply #'evil-concat-keymap-alists result)
|
||
|
(apply #'append result))))
|
||
|
|
||
|
(defun evil-normalize-keymaps (&optional state)
|
||
|
"Create a buffer-local value for `evil-mode-map-alist'.
|
||
|
This is a keymap alist, determined by the current state
|
||
|
\(or by STATE if specified)."
|
||
|
(let ((state (or state evil-state))
|
||
|
(excluded '(nil t))
|
||
|
map mode temp)
|
||
|
;; initialize buffer-local keymaps as necessary
|
||
|
(evil-initialize-local-keymaps)
|
||
|
;; deactivate keymaps of previous state
|
||
|
(dolist (entry evil-mode-map-alist)
|
||
|
(setq mode (car-safe entry)
|
||
|
map (cdr-safe entry))
|
||
|
;; don't deactivate overriding keymaps;
|
||
|
;; they are toggled by their associated mode
|
||
|
(if (or (memq mode excluded)
|
||
|
(evil-intercept-keymap-p map)
|
||
|
(evil-overriding-keymap-p map)
|
||
|
(evil-auxiliary-keymap-p map))
|
||
|
(push mode excluded)
|
||
|
(when (and (fboundp mode) (symbol-value mode))
|
||
|
(funcall mode -1))
|
||
|
(set mode nil)))
|
||
|
(setq evil-mode-map-alist nil)
|
||
|
;; activate keymaps of current state
|
||
|
(when state
|
||
|
(setq temp (evil-state-keymaps state))
|
||
|
(dolist (entry temp)
|
||
|
(setq mode (car entry)
|
||
|
map (cdr entry))
|
||
|
(unless (and (boundp mode) (symbol-value mode))
|
||
|
(when (fboundp mode)
|
||
|
(funcall mode 1))
|
||
|
(set mode t))
|
||
|
;; refresh the keymap in case it has changed
|
||
|
;; (e.g., `evil-operator-shortcut-map' is
|
||
|
;; reset on toggling)
|
||
|
(if (or (memq mode excluded)
|
||
|
(evil-intercept-keymap-p map)
|
||
|
(evil-overriding-keymap-p map)
|
||
|
(evil-auxiliary-keymap-p map))
|
||
|
(push mode excluded)
|
||
|
(setcdr entry (or (evil-keymap-for-mode mode) map))))
|
||
|
;; update `evil-mode-map-alist'
|
||
|
(setq evil-mode-map-alist temp))))
|
||
|
|
||
|
(defun evil-mode-for-keymap (keymap &optional default)
|
||
|
"Return the minor mode associated with KEYMAP.
|
||
|
Returns DEFAULT if no mode is found.
|
||
|
See also `evil-keymap-for-mode'."
|
||
|
(let ((map (if (keymapp keymap) keymap (symbol-value keymap)))
|
||
|
(var (when (symbolp keymap) keymap)))
|
||
|
;; Check Evil variables first for speed purposes.
|
||
|
;; If all else fails, check `minor-mode-map-alist'.
|
||
|
(or (when var
|
||
|
(or (car (rassq var evil-global-keymaps-alist))
|
||
|
(car (rassq var evil-local-keymaps-alist))))
|
||
|
(car (rassq map (mapcar #'(lambda (e)
|
||
|
;; from (MODE-VAR . MAP-VAR)
|
||
|
;; to (MODE-VAR . MAP)
|
||
|
(cons (car-safe e)
|
||
|
(symbol-value (cdr-safe e))))
|
||
|
(append evil-global-keymaps-alist
|
||
|
evil-local-keymaps-alist))))
|
||
|
(car (rassq map minor-mode-map-alist))
|
||
|
default)))
|
||
|
|
||
|
(defun evil-keymap-for-mode (mode &optional variable)
|
||
|
"Return the keymap associated with MODE.
|
||
|
Return the keymap variable if VARIABLE is non-nil.
|
||
|
See also `evil-mode-for-keymap'."
|
||
|
(let* ((var (or (cdr (assq mode evil-global-keymaps-alist))
|
||
|
(cdr (assq mode evil-local-keymaps-alist))))
|
||
|
(map (or (symbol-value var)
|
||
|
(cdr (assq mode minor-mode-map-alist)))))
|
||
|
(if variable var map)))
|
||
|
|
||
|
(defun evil-state-auxiliary-keymaps (state)
|
||
|
"Return a keymap alist of auxiliary keymaps for STATE."
|
||
|
(let ((state (or state evil-state))
|
||
|
aux result)
|
||
|
(dolist (map (current-active-maps) result)
|
||
|
(when (setq aux (evil-get-auxiliary-keymap map state))
|
||
|
(push (cons (evil-mode-for-keymap map t) aux) result)))
|
||
|
(nreverse result)))
|
||
|
|
||
|
(defun evil-state-overriding-keymaps (&optional state)
|
||
|
"Return a keymap alist of overriding keymaps for STATE."
|
||
|
(let* ((state (or state evil-state))
|
||
|
result)
|
||
|
(dolist (map (current-active-maps))
|
||
|
(when (setq map (evil-overriding-keymap-p map state))
|
||
|
(push (cons (evil-mode-for-keymap map t) map) result)))
|
||
|
(nreverse result)))
|
||
|
|
||
|
(defun evil-state-intercept-keymaps (&optional state)
|
||
|
"Return a keymap alist of intercept keymaps for STATE."
|
||
|
(let* ((state (or state evil-state))
|
||
|
result)
|
||
|
(dolist (map (current-active-maps))
|
||
|
(when (setq map (evil-intercept-keymap-p map state))
|
||
|
(push (cons (evil-mode-for-keymap map t) map) result)))
|
||
|
(setq result (nreverse result))
|
||
|
(add-to-list 'result `(evil-esc-mode . ,evil-esc-map))
|
||
|
result))
|
||
|
|
||
|
(defun evil-set-auxiliary-keymap (map state &optional aux)
|
||
|
"Set the auxiliary keymap for MAP in STATE to AUX.
|
||
|
If AUX is nil, create a new auxiliary keymap."
|
||
|
(unless (keymapp aux)
|
||
|
(setq aux (make-sparse-keymap)))
|
||
|
(unless (evil-auxiliary-keymap-p aux)
|
||
|
(evil-set-keymap-prompt
|
||
|
aux (format "Auxiliary keymap for %s"
|
||
|
(or (evil-state-property state :name)
|
||
|
(format "%s state" state)))))
|
||
|
(define-key map
|
||
|
(vconcat (list (intern (format "%s-state" state)))) aux)
|
||
|
aux)
|
||
|
(put 'evil-set-auxiliary-keymap 'lisp-indent-function 'defun)
|
||
|
|
||
|
(defun evil-get-auxiliary-keymap (map state &optional create)
|
||
|
"Get the auxiliary keymap for MAP in STATE.
|
||
|
If CREATE is non-nil, create an auxiliary keymap
|
||
|
if MAP does not have one."
|
||
|
(when state
|
||
|
(let* ((key (vconcat (list (intern (format "%s-state" state)))))
|
||
|
(aux (if state (lookup-key map key) map)))
|
||
|
(cond
|
||
|
((evil-auxiliary-keymap-p aux)
|
||
|
aux)
|
||
|
(create
|
||
|
(evil-set-auxiliary-keymap map state))))))
|
||
|
|
||
|
(defun evil-auxiliary-keymap-p (map)
|
||
|
"Whether MAP is an auxiliary keymap."
|
||
|
(and (keymapp map)
|
||
|
(string-match "Auxiliary keymap"
|
||
|
(or (keymap-prompt map) "")) t))
|
||
|
|
||
|
(defun evil-intercept-keymap-p (map &optional state)
|
||
|
"Whether MAP is an intercept keymap for STATE.
|
||
|
If STATE is nil, it means any state."
|
||
|
(let ((entry (and (keymapp map)
|
||
|
(lookup-key map [intercept-state]))))
|
||
|
(cond
|
||
|
((null entry)
|
||
|
nil)
|
||
|
((null state)
|
||
|
map)
|
||
|
((eq entry state)
|
||
|
map)
|
||
|
((eq entry 'all)
|
||
|
map))))
|
||
|
|
||
|
(defun evil-overriding-keymap-p (map &optional state)
|
||
|
"Whether MAP is an overriding keymap for STATE.
|
||
|
If STATE is nil, it means any state."
|
||
|
(let ((entry (and (keymapp map)
|
||
|
(lookup-key map [override-state]))))
|
||
|
(cond
|
||
|
((null entry)
|
||
|
nil)
|
||
|
((keymapp entry)
|
||
|
(evil-overriding-keymap-p entry state))
|
||
|
((null state)
|
||
|
map)
|
||
|
((eq entry state)
|
||
|
map)
|
||
|
((eq entry 'all)
|
||
|
map))))
|
||
|
|
||
|
(defun evil-intercept-keymap-state (map)
|
||
|
"Return the state for the intercept keymap MAP.
|
||
|
A return value of t means all states."
|
||
|
(let ((state (lookup-key map [intercept-state] map)))
|
||
|
(cond
|
||
|
((keymapp state)
|
||
|
(evil-intercept-keymap-state state))
|
||
|
((eq state 'all)
|
||
|
t)
|
||
|
(t
|
||
|
state))))
|
||
|
|
||
|
(defun evil-overriding-keymap-state (map)
|
||
|
"Return the state for the overriding keymap MAP.
|
||
|
A return value of t means all states."
|
||
|
(let ((state (lookup-key map [override-state] map)))
|
||
|
(cond
|
||
|
((keymapp state)
|
||
|
(evil-overriding-keymap-state state))
|
||
|
((eq state 'all)
|
||
|
t)
|
||
|
(t
|
||
|
state))))
|
||
|
|
||
|
(defmacro evil-define-key (state keymap key def &rest bindings)
|
||
|
"Create a STATE binding from KEY to DEF for KEYMAP.
|
||
|
STATE is one of `normal', `insert', `visual', `replace',
|
||
|
`operator', `motion' and `emacs'. The remaining arguments
|
||
|
are like those of `define-key'. For example:
|
||
|
|
||
|
(evil-define-key 'normal foo-map \"a\" 'bar)
|
||
|
|
||
|
This creates a binding from \"a\" to `bar' in Normal state,
|
||
|
which is active whenever `foo-map' is active. It is possible
|
||
|
to specify multiple bindings at once:
|
||
|
|
||
|
(evil-define-key 'normal foo-map
|
||
|
\"a\" 'bar
|
||
|
\"b\" 'foo)
|
||
|
|
||
|
If foo-map has not been initialized yet, this macro adds an entry
|
||
|
to `after-load-functions', delaying execution as necessary."
|
||
|
(declare (indent defun))
|
||
|
`(evil-delay ',(if (symbolp keymap)
|
||
|
`(and (boundp ',keymap) (keymapp ,keymap))
|
||
|
`(keymapp ,keymap))
|
||
|
'(let* ((state ,state) (keymap ,keymap) (key ,key) (def ,def)
|
||
|
(bindings (list ,@bindings)) aux)
|
||
|
(if state
|
||
|
(setq aux (evil-get-auxiliary-keymap keymap state t))
|
||
|
(setq aux keymap))
|
||
|
(while key
|
||
|
(define-key aux key def)
|
||
|
(setq key (pop bindings)
|
||
|
def (pop bindings)))
|
||
|
;; ensure the prompt string comes first
|
||
|
(evil-set-keymap-prompt aux (keymap-prompt aux)))
|
||
|
'after-load-functions t nil
|
||
|
(format "evil-define-key-in-%s"
|
||
|
',(if (symbolp keymap) keymap 'keymap))))
|
||
|
(defalias 'evil-declare-key 'evil-define-key)
|
||
|
|
||
|
(defmacro evil-add-hjkl-bindings (keymap &optional state &rest bindings)
|
||
|
"Add \"h\", \"j\", \"k\", \"l\" bindings to KEYMAP in STATE.
|
||
|
Add additional BINDINGS if specified."
|
||
|
(declare (indent defun))
|
||
|
`(evil-define-key ,state ,keymap
|
||
|
"h" (lookup-key evil-motion-state-map "h")
|
||
|
"j" (lookup-key evil-motion-state-map "j")
|
||
|
"k" (lookup-key evil-motion-state-map "k")
|
||
|
"l" (lookup-key evil-motion-state-map "l")
|
||
|
":" (lookup-key evil-motion-state-map ":")
|
||
|
,@bindings))
|
||
|
|
||
|
;; may be useful for programmatic purposes
|
||
|
(defun evil-global-set-key (state key def)
|
||
|
"Bind KEY to DEF in STATE."
|
||
|
(define-key (evil-state-property state :keymap t) key def))
|
||
|
|
||
|
(defun evil-local-set-key (state key def)
|
||
|
"Bind KEY to DEF in STATE in the current buffer."
|
||
|
(define-key (evil-state-property state :local-keymap t) key def))
|
||
|
|
||
|
;; Advise these functions as they may activate an overriding keymap or
|
||
|
;; a keymap with state bindings; if so, refresh `evil-mode-map-alist'.
|
||
|
(defadvice use-global-map (after evil activate)
|
||
|
"Refresh Evil keymaps."
|
||
|
(evil-normalize-keymaps))
|
||
|
|
||
|
(defadvice use-local-map (after evil activate)
|
||
|
"Refresh Evil keymaps."
|
||
|
(evil-normalize-keymaps))
|
||
|
|
||
|
(defmacro evil-define-state (state doc &rest body)
|
||
|
"Define an Evil state STATE.
|
||
|
DOC is a general description and shows up in all docstrings;
|
||
|
the first line of the string should be the full name of the state.
|
||
|
Then follows one or more optional keywords:
|
||
|
|
||
|
:tag STRING Mode line indicator.
|
||
|
:message STRING Echo area message when changing to STATE.
|
||
|
:cursor SPEC Cursor to use in STATE.
|
||
|
:entry-hook LIST Hooks run when changing to STATE.
|
||
|
:exit-hook LIST Hooks run when changing from STATE.
|
||
|
:enable LIST List of other states and modes enabled by STATE.
|
||
|
:suppress-keymap FLAG If FLAG is non-nil, makes `evil-suppress-map'
|
||
|
the parent of the global map of STATE,
|
||
|
effectively disabling bindings to
|
||
|
`self-insert-command'.
|
||
|
|
||
|
Following the keywords is optional code to be executed each time
|
||
|
the state is enabled or disabled. For example:
|
||
|
|
||
|
(evil-define-state test
|
||
|
\"Test state.\"
|
||
|
:tag \"<T> \"
|
||
|
(setq test-var t))
|
||
|
|
||
|
The global keymap of this state will be `evil-test-state-map',
|
||
|
the local keymap will be `evil-test-state-local-map', and so on.
|
||
|
|
||
|
\(fn STATE DOC [[KEY VAL]...] BODY...)"
|
||
|
(declare (indent defun)
|
||
|
(debug (&define name
|
||
|
[&optional stringp]
|
||
|
[&rest [keywordp sexp]]
|
||
|
def-body)))
|
||
|
(let* ((name (and (string-match "^\\(.+\\)\\(\\(?:.\\|\n\\)*\\)" doc)
|
||
|
(match-string 1 doc)))
|
||
|
(doc (match-string 2 doc))
|
||
|
(name (and (string-match "^\\(.+?\\)\\.?$" name)
|
||
|
(match-string 1 name)))
|
||
|
(doc (if (or (null doc) (string= doc "")) ""
|
||
|
(format "\n%s" doc)))
|
||
|
(toggle (intern (format "evil-%s-state" state)))
|
||
|
(mode (intern (format "%s-minor-mode" toggle)))
|
||
|
(keymap (intern (format "%s-map" toggle)))
|
||
|
(local (intern (format "%s-local-minor-mode" toggle)))
|
||
|
(local-keymap (intern (format "%s-local-map" toggle)))
|
||
|
(tag (intern (format "%s-tag" toggle)))
|
||
|
(message (intern (format "%s-message" toggle)))
|
||
|
(cursor (intern (format "%s-cursor" toggle)))
|
||
|
(entry-hook (intern (format "%s-entry-hook" toggle)))
|
||
|
(exit-hook (intern (format "%s-exit-hook" toggle)))
|
||
|
(modes (intern (format "%s-modes" toggle)))
|
||
|
(predicate (intern (format "%s-p" toggle)))
|
||
|
(intercept-esc t)
|
||
|
arg cursor-value enable entry-hook-value exit-hook-value
|
||
|
input-method key message-value suppress-keymap tag-value)
|
||
|
;; collect keywords
|
||
|
(while (keywordp (car-safe body))
|
||
|
(setq key (pop body)
|
||
|
arg (pop body))
|
||
|
(cond
|
||
|
((eq key :tag)
|
||
|
(setq tag-value arg))
|
||
|
((eq key :message)
|
||
|
(setq message-value arg))
|
||
|
((eq key :cursor)
|
||
|
(setq cursor-value arg))
|
||
|
((eq key :entry-hook)
|
||
|
(setq entry-hook-value arg)
|
||
|
(unless (listp entry-hook-value)
|
||
|
(setq entry-hook-value (list entry-hook-value))))
|
||
|
((eq key :exit-hook)
|
||
|
(setq exit-hook-value arg)
|
||
|
(unless (listp exit-hook-value)
|
||
|
(setq exit-hook-value (list entry-hook-value))))
|
||
|
((eq key :enable)
|
||
|
(setq enable arg))
|
||
|
((eq key :input-method)
|
||
|
(setq input-method arg))
|
||
|
((eq key :intercept-esc)
|
||
|
(setq intercept-esc arg))
|
||
|
((eq key :suppress-keymap)
|
||
|
(setq suppress-keymap arg))))
|
||
|
|
||
|
;; macro expansion
|
||
|
`(progn
|
||
|
;; Save the state's properties in `evil-state-properties' for
|
||
|
;; runtime lookup. Among other things, this information is used
|
||
|
;; to determine what keymaps should be activated by the state
|
||
|
;; (and, when processing :enable, what keymaps are activated by
|
||
|
;; other states). We cannot know this at compile time because
|
||
|
;; it depends on the current buffer and its active keymaps
|
||
|
;; (to which we may have assigned state bindings), as well as
|
||
|
;; states whose definitions may not have been processed yet.
|
||
|
(evil-put-property
|
||
|
'evil-state-properties ',state
|
||
|
:name ',name
|
||
|
:toggle ',toggle
|
||
|
:mode (defvar ,mode nil
|
||
|
,(format "Non-nil if %s is enabled.
|
||
|
Use the command `%s' to change this variable." name toggle))
|
||
|
:keymap (defvar ,keymap (make-sparse-keymap)
|
||
|
,(format "Keymap for %s." name))
|
||
|
:local (defvar ,local nil
|
||
|
,(format "Non-nil if %s is enabled.
|
||
|
Use the command `%s' to change this variable." name toggle))
|
||
|
:local-keymap (defvar ,local-keymap nil
|
||
|
,(format "Buffer-local keymap for %s." name))
|
||
|
:tag (defvar ,tag ,tag-value
|
||
|
,(format "Mode line tag for %s." name))
|
||
|
:message (defvar ,message ,message-value
|
||
|
,(format "Echo area message for %s." name))
|
||
|
:cursor (defvar ,cursor ',cursor-value
|
||
|
,(format "Cursor for %s.
|
||
|
May be a cursor type as per `cursor-type', a color string as passed
|
||
|
to `set-cursor-color', a zero-argument function for changing the
|
||
|
cursor, or a list of the above." name))
|
||
|
:entry-hook (defvar ,entry-hook nil
|
||
|
,(format "Hooks to run when entering %s." name))
|
||
|
:exit-hook (defvar ,exit-hook nil
|
||
|
,(format "Hooks to run when exiting %s." name))
|
||
|
:modes (defvar ,modes nil
|
||
|
,(format "Modes that should come up in %s." name))
|
||
|
:input-method ',input-method
|
||
|
:predicate ',predicate
|
||
|
:enable ',enable)
|
||
|
|
||
|
,@(when suppress-keymap
|
||
|
`((set-keymap-parent ,keymap evil-suppress-map)))
|
||
|
|
||
|
(dolist (func ',entry-hook-value)
|
||
|
(add-hook ',entry-hook func))
|
||
|
|
||
|
(dolist (func ',exit-hook-value)
|
||
|
(add-hook ',exit-hook func))
|
||
|
|
||
|
(defun ,predicate (&optional state)
|
||
|
,(format "Whether the current state is %s.
|
||
|
\(That is, whether `evil-state' is `%s'.)" name state)
|
||
|
(and evil-local-mode
|
||
|
(eq (or state evil-state) ',state)))
|
||
|
|
||
|
;; define state function
|
||
|
(defun ,toggle (&optional arg)
|
||
|
,(format "Enable %s. Disable with negative ARG.
|
||
|
If ARG is nil, don't display a message in the echo area.%s" name doc)
|
||
|
(interactive "p")
|
||
|
(cond
|
||
|
((and (numberp arg) (< arg 1))
|
||
|
(setq evil-previous-state evil-state
|
||
|
evil-state nil)
|
||
|
(let ((evil-state ',state))
|
||
|
(run-hooks ',exit-hook)
|
||
|
(setq evil-state nil)
|
||
|
(evil-esc-mode -1)
|
||
|
(evil-normalize-keymaps)
|
||
|
,@body))
|
||
|
(t
|
||
|
(unless evil-local-mode
|
||
|
(evil-local-mode 1))
|
||
|
(let ((evil-next-state ',state)
|
||
|
input-method-activate-hook
|
||
|
input-method-deactivate-hook)
|
||
|
(evil-change-state nil)
|
||
|
(setq evil-state ',state)
|
||
|
(evil-add-to-alist 'evil-previous-state-alist
|
||
|
',state evil-previous-state)
|
||
|
(let ((evil-state ',state))
|
||
|
(evil-normalize-keymaps)
|
||
|
(if ,intercept-esc
|
||
|
(evil-esc-mode 1)
|
||
|
(evil-esc-mode -1))
|
||
|
(if ',input-method
|
||
|
(activate-input-method evil-input-method)
|
||
|
(deactivate-input-method))
|
||
|
(unless evil-no-display
|
||
|
(evil-refresh-cursor ',state)
|
||
|
(evil-refresh-mode-line ',state)
|
||
|
(when (evil-called-interactively-p)
|
||
|
(redisplay)))
|
||
|
,@body
|
||
|
(run-hooks ',entry-hook)
|
||
|
(when (and evil-echo-state
|
||
|
arg (not evil-no-display) ,message)
|
||
|
(if (functionp ,message)
|
||
|
(funcall ,message)
|
||
|
(evil-echo "%s" ,message))))))))
|
||
|
|
||
|
(evil-set-command-property ',toggle :keep-visual t)
|
||
|
(evil-set-command-property ',toggle :suppress-operator t)
|
||
|
|
||
|
(evil-define-keymap ,keymap nil
|
||
|
:mode ,mode
|
||
|
:func nil)
|
||
|
|
||
|
(evil-define-keymap ,local-keymap nil
|
||
|
:mode ,local
|
||
|
:local t
|
||
|
:func nil)
|
||
|
|
||
|
',state)))
|
||
|
|
||
|
(provide 'evil-core)
|
||
|
|
||
|
;;; evil-core.el ends here
|