Function: define-minor-mode
define-minor-mode is an autoloaded macro defined in easy-mmode.el.gz.
Signature
(define-minor-mode MODE DOC [KEYWORD VAL ... &rest BODY])
Documentation
Define a new minor mode MODE.
This defines the toggle command MODE and (by default) a control variable MODE (you can override this with the :variable keyword, see below). DOC is the documentation for the mode toggle command.
The defined mode command takes one optional (prefix) argument. Interactively with no prefix argument, it toggles the mode. A prefix argument enables the mode if the argument is positive, and disables it otherwise.
When called from Lisp, the mode command toggles the mode if the
argument is toggle, disables the mode if the argument is a
non-positive integer, and enables the mode otherwise (including
if the argument is omitted or nil or a positive integer).
If DOC is nil, give the mode command a basic doc-string documenting what its argument does. If the word "ARG" does not appear in DOC, a paragraph is added to DOC explaining usage of the mode argument.
BODY contains code to execute each time the mode is enabled or disabled.
It is executed after toggling the mode, and before running MODE-hook.
Before the actual body code, you can write keyword arguments, i.e.
alternating keywords and values. If you provide BODY, then you must
provide at least one keyword argument (e.g. `:lighter nil`).
The following special keywords are supported (other keywords are passed
to defcustom if the minor mode is global):
:global GLOBAL If non-nil specifies that the minor mode is not meant to be
buffer-local, so don't make the variable MODE buffer-local.
By default, the mode is buffer-local.
:init-value VAL the initial value of the mode's variable.
Note that the minor mode function won't be called by setting
this option, so the value *reflects* the minor mode's natural
initial state, rather than *setting* it.
In the vast majority of cases it should be nil.
Not used if you also specify :variable.
:lighter SPEC Text displayed in the mode line when the mode is on.
:keymap MAP Keymap bound to the mode keymap. Defaults to MODE-map.
If non-nil, it should be an unquoted variable name (whose value
is a keymap), or an expression that returns either a keymap or
a list of (KEY . BINDING) pairs where KEY and BINDING are
suitable for define-key. If you supply a KEYMAP argument
that is not a symbol, this macro defines the variable MODE-map
and gives it the value that KEYMAP specifies.
:interactive VAL Whether this mode should be a command or not. The default
is to make it one; use nil to avoid that. If VAL is a list,
it's interpreted as a list of major modes this minor mode
is useful in.
:variable PLACE The location to use instead of the variable MODE to store
the state of the mode. This can be simply a different
named variable, or a generalized variable.
PLACE can also be of the form (GET . SET), where GET is
an expression that returns the current state, and SET is
a function that takes one argument, the new state, which should
be assigned to PLACE. If you specify a :variable, this function
does not define a MODE variable (nor any of the terms used
in :variable).
:after-hook A single Lisp form which is evaluated after the mode hooks
have been run. It should not be quoted.
For example, you could write
(define-minor-mode foo-mode "If enabled, foo on you!"
:lighter " Foo" :require 'foo :global t :group 'hassle :version "27.5"
...BODY CODE...)
For backward compatibility with the Emacs<21 calling convention, the keywords can also be preceded by the obsolete triplet INIT-VALUE LIGHTER KEYMAP.
Probably introduced at or before Emacs version 21.1.
Aliases
easy-mmode-define-minor-mode (obsolete since 30.1)
erc-define-minor-mode (obsolete since 28.1)
Source Code
;; Defined in /usr/src/emacs/lisp/emacs-lisp/easy-mmode.el.gz
;;;###autoload
(defmacro define-minor-mode (mode doc &rest body)
"Define a new minor mode MODE.
This defines the toggle command MODE and (by default) a control variable
MODE (you can override this with the :variable keyword, see below).
DOC is the documentation for the mode toggle command.
The defined mode command takes one optional (prefix) argument.
Interactively with no prefix argument, it toggles the mode.
A prefix argument enables the mode if the argument is positive,
and disables it otherwise.
When called from Lisp, the mode command toggles the mode if the
argument is `toggle', disables the mode if the argument is a
non-positive integer, and enables the mode otherwise (including
if the argument is omitted or nil or a positive integer).
If DOC is nil, give the mode command a basic doc-string
documenting what its argument does. If the word \"ARG\" does not
appear in DOC, a paragraph is added to DOC explaining
usage of the mode argument.
BODY contains code to execute each time the mode is enabled or disabled.
It is executed after toggling the mode, and before running MODE-hook.
Before the actual body code, you can write keyword arguments, i.e.
alternating keywords and values. If you provide BODY, then you must
provide at least one keyword argument (e.g. `:lighter nil`).
The following special keywords are supported (other keywords are passed
to `defcustom' if the minor mode is global):
:global GLOBAL If non-nil specifies that the minor mode is not meant to be
buffer-local, so don't make the variable MODE buffer-local.
By default, the mode is buffer-local.
:init-value VAL the initial value of the mode's variable.
Note that the minor mode function won't be called by setting
this option, so the value *reflects* the minor mode's natural
initial state, rather than *setting* it.
In the vast majority of cases it should be nil.
Not used if you also specify :variable.
:lighter SPEC Text displayed in the mode line when the mode is on.
:keymap MAP Keymap bound to the mode keymap. Defaults to `MODE-map'.
If non-nil, it should be an unquoted variable name (whose value
is a keymap), or an expression that returns either a keymap or
a list of (KEY . BINDING) pairs where KEY and BINDING are
suitable for `define-key'. If you supply a KEYMAP argument
that is not a symbol, this macro defines the variable MODE-map
and gives it the value that KEYMAP specifies.
:interactive VAL Whether this mode should be a command or not. The default
is to make it one; use nil to avoid that. If VAL is a list,
it's interpreted as a list of major modes this minor mode
is useful in.
:variable PLACE The location to use instead of the variable MODE to store
the state of the mode. This can be simply a different
named variable, or a generalized variable.
PLACE can also be of the form (GET . SET), where GET is
an expression that returns the current state, and SET is
a function that takes one argument, the new state, which should
be assigned to PLACE. If you specify a :variable, this function
does not define a MODE variable (nor any of the terms used
in :variable).
:after-hook A single Lisp form which is evaluated after the mode hooks
have been run. It should not be quoted.
For example, you could write
(define-minor-mode foo-mode \"If enabled, foo on you!\"
:lighter \" Foo\" :require \\='foo :global t :group \\='hassle :version \"27.5\"
...BODY CODE...)
For backward compatibility with the Emacs<21 calling convention,
the keywords can also be preceded by the obsolete triplet
INIT-VALUE LIGHTER KEYMAP.
\(fn MODE DOC [KEYWORD VAL ... &rest BODY])"
(declare (doc-string 2)
(indent defun)
(debug (&define name string-or-null-p
[&optional [¬ keywordp] sexp
&optional [¬ keywordp] sexp
&optional [¬ keywordp] sexp]
[&rest [keywordp sexp]]
def-body))
;; expand to the command definition on autoload gen
(autoload-macro expand))
(let* ((last-message (make-symbol "last-message"))
(mode-name (symbol-name mode))
(init-value nil)
(keymap nil)
(lighter nil)
(pretty-name nil)
(globalp nil)
(set nil)
(initialize nil)
(type nil)
(extra-args nil)
(extra-keywords nil)
(variable nil) ;The PLACE where the state is stored.
(setter `(setq ,mode)) ;The beginning of the exp to set the mode var.
(getter mode) ;The exp to get the mode value.
(modefun mode) ;The minor mode function name we're defining.
(after-hook nil)
(hook (intern (concat mode-name "-hook")))
(hook-on (intern (concat mode-name "-on-hook")))
(hook-off (intern (concat mode-name "-off-hook")))
(interactive t)
(new-style
(or (null body)
(keywordp (car body))))
(warnwrap (if new-style #'identity
(lambda (exp)
(macroexp-warn-and-return
(format-message
"Use keywords rather than deprecated positional arguments to `define-minor-mode'")
exp))))
keyw keymap-sym tmp)
;; Allow BODY to start with the old INIT-VALUE LIGHTER KEYMAP triplet.
(unless new-style
(setq init-value (pop body))
(unless (keywordp (car body))
(setq lighter (pop body))
(unless (keywordp (car body))
(setq keymap (pop body)))))
;; Check keys.
(while (keywordp (setq keyw (car body)))
(setq body (cdr body))
(pcase keyw
(:init-value (setq init-value (pop body)))
(:lighter (setq lighter (pop body)))
(:global (setq globalp (pop body))
(when (and globalp (symbolp mode))
(setq setter `(setq-default ,mode))
(setq getter `(default-value ',mode))))
(:extra-args (setq extra-args (pop body)))
(:set (setq set (list :set (pop body))))
(:initialize (setq initialize (list :initialize (pop body))))
(:type (setq type (list :type (pop body))))
(:keymap (setq keymap (pop body)))
(:interactive (setq interactive (pop body)))
(:variable (setq variable (pop body))
(if (not (and (setq tmp (cdr-safe variable))
(or (symbolp tmp)
(functionp tmp))))
;; PLACE is not of the form (GET . SET).
(progn
(setq setter `(setf ,variable))
(setq getter variable))
(setq getter (car variable))
(setq setter `(funcall #',(cdr variable)))))
(:after-hook (setq after-hook (pop body)))
(_ (push keyw extra-keywords) (push (pop body) extra-keywords))))
(setq pretty-name (easy-mmode-pretty-mode-name mode lighter))
(setq keymap-sym (if (and keymap (symbolp keymap)) keymap
(intern (concat mode-name "-map"))))
(unless set (setq set '(:set #'custom-set-minor-mode)))
(unless initialize
(setq initialize '(:initialize #'custom-initialize-default)))
;; TODO? Mark booleans as safe if booleanp? Eg abbrev-mode.
(unless type (setq type '(:type 'boolean)))
`(progn
;; Define the variable to enable or disable the mode.
,(cond
;; If :variable is specified, then the var will be
;; declared elsewhere.
(variable nil)
((not globalp)
`(progn
:autoload-end
(defvar-local ,mode ,init-value
,(concat (format "Non-nil if %s is enabled.\n" pretty-name)
(internal--format-docstring-line
"Use the command `%s' to change this variable." mode)))))
(t
(let ((base-doc-string
(concat "Non-nil if %s is enabled.
See the `%s' command
for a description of this minor mode."
(if body "
Setting this variable directly does not take effect;
either customize it (see the info node `Easy Customization')
or call the function `%s'."))))
`(progn
(defcustom ,mode ,init-value
,(format base-doc-string pretty-name mode mode)
,@set
,@initialize
,@type
,@(nreverse extra-keywords))
,(when init-value
`(when (bound-and-true-p ,mode)
(add-to-list 'global-minor-modes ',modefun)))))))
;; The actual function.
,(funcall
warnwrap
`(defun ,modefun (&optional arg ,@extra-args)
,(easy-mmode--mode-docstring doc pretty-name keymap-sym
getter globalp)
,(when interactive
;; Use `toggle' rather than (if ,mode 0 1) so that using
;; repeat-command still does the toggling correctly.
(if (consp interactive)
`(interactive
(list (if current-prefix-arg
(prefix-numeric-value current-prefix-arg)
'toggle))
,@interactive)
'(interactive
(list (if current-prefix-arg
(prefix-numeric-value current-prefix-arg)
'toggle)))))
(let ((,last-message (current-message)))
(,@setter
(cond ((eq arg 'toggle) (not ,getter))
(t (not (and (numberp arg) (< arg 1))))))
;; Keep minor modes list up to date.
,(let ((minor-modes-var (if globalp
'global-minor-modes
'local-minor-modes)))
;; When running this byte-compiled code in earlier
;; Emacs versions, these variables may not be defined
;; there. So check defensively, even if they're
;; always defined in Emacs 28 and up.
`(when (boundp ',minor-modes-var)
(if ,getter
(add-to-list ',minor-modes-var ',modefun)
(setq ,minor-modes-var
(delq ',modefun ,minor-modes-var)))))
,@body
;; The on/off hooks are here for backward compatibility only.
(run-hooks ',hook (if ,getter ',hook-on ',hook-off))
(if (called-interactively-p 'any)
(progn
,(if (and globalp (not variable))
`(customize-mark-as-set ',mode))
;; Avoid overwriting a message shown by the body,
;; but do overwrite previous messages.
(unless (and (current-message)
(not (equal ,last-message
(current-message))))
(let ((local ,(if globalp "" " in current buffer")))
(message "%s %sabled%s" ,pretty-name
(if ,getter "en" "dis") local)))))
,@(when after-hook `(,after-hook)))
(force-mode-line-update)
;; Return the new setting.
,getter))
;; Autoloading a define-minor-mode autoloads everything
;; up-to-here.
:autoload-end
(defvar ,hook nil)
(unless (get ',hook 'variable-documentation)
(put ',hook 'variable-documentation
,(format "Hook run after entering or leaving `%s'.
No problems result if this variable is not bound.
`add-hook' automatically binds it. (This is true for all hook variables.)"
modefun)))
;; Allow using `M-x customize-variable' on the hook.
(put ',hook 'custom-type 'hook)
(put ',hook 'standard-value (list nil))
;; Define the minor-mode keymap.
,(unless (symbolp keymap) ;nil is also a symbol.
`(defvar ,keymap-sym
(let ((m ,keymap))
(cond ((keymapp m) m)
;; FIXME: `easy-mmode-define-keymap' is obsolete,
;; so this form should also be obsolete somehow.
((listp m)
(with-suppressed-warnings ((obsolete
easy-mmode-define-keymap))
(easy-mmode-define-keymap m)))
(t (error "Invalid keymap %S" m))))
,(format "Keymap for `%s'." mode-name)))
,(let ((modevar (pcase getter (`(default-value ',v) v) (_ getter))))
(if (not (symbolp modevar))
(if (or lighter keymap)
(error ":lighter and :keymap unsupported with mode expression %S" getter))
`(with-no-warnings
(add-minor-mode ',modevar ',lighter
,(if keymap keymap-sym
`(if (boundp ',keymap-sym) ,keymap-sym))
nil
,(unless (eq mode modefun) `',modefun))))))))