Function: evil-define-state

evil-define-state is a macro defined in evil-core.el.

Signature

(evil-define-state STATE DOC [[KEY VAL]...] BODY...)

Documentation

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.

BODY is executed each time the state is enabled or disabled.

Optional keyword arguments:
- :tag - the mode line indicator, e.g. "<T>".
- :message - string shown in the echo area when the state is
  activated.
- :cursor - default cursor specification.
- :enable - list of other state keymaps to enable when in this
  state.
- :entry-hook - list of functions to run when entering this state.
- :exit-hook - list of functions to run when exiting this state.
- :suppress-keymap - if non-nil, effectively disables bindings to
  self-insert-command by making evil-suppress-map the parent of
  the global state keymap.

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.

Source Code

;; Defined in ~/.emacs.d/elpa/evil-20251108.138/evil-core.el
(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.

BODY is executed each time the state is enabled or disabled.

Optional keyword arguments:
- `:tag' - the mode line indicator, e.g. \"<T>\".
- `:message' - string shown in the echo area when the state is
  activated.
- `:cursor' - default cursor specification.
- `:enable' - list of other state keymaps to enable when in this
  state.
- `:entry-hook' - list of functions to run when entering this state.
- `:exit-hook' - list of functions to run when exiting this state.
- `:suppress-keymap' - if non-nil, effectively disables bindings to
  `self-insert-command' by making `evil-suppress-map' the parent of
  the global state keymap.

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)
           (doc-string 2)
           (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)))
         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 exit-hook-value))))
       ((eq key :enable)
        (setq enable arg))
       ((eq key :input-method)
        (setq input-method 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.
       (let ((plist (list
                     :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)))
       (evil--add-to-alist evil-state-properties ',state plist))

       ,@(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)
         (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-normalize-keymaps)
             ,@body))
          (t
           (unless evil-local-mode (evil-local-mode))
           (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 ',input-method
                   (activate-input-method evil-input-method)
                 ;; BUG #475: Deactivate the current input method only
                 ;; if there is a function to deactivate it, otherwise
                 ;; an error would be raised. This strange situation
                 ;; should not arise in general and there should
                 ;; probably be a better way to handle this situation.
                 (when deactivate-current-input-method-function
                   (deactivate-input-method)))
               (unless evil-no-display
                 (when (eq (window-buffer) (current-buffer))
                   (evil-refresh-cursor ',state))
                 (evil-refresh-mode-line ',state))
               ,@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-add-command-properties ',toggle :keep-visual t :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)))