-;;; easy-mmode.el --- easy definition for major and minor modes.
+;;; easy-mmode.el --- easy definition for major and minor modes
-;; Copyright (C) 1997,2000 Free Software Foundation, Inc.
+;; Copyright (C) 1997,2000,01,02,03,2004 Free Software Foundation, Inc.
-;; Author: Georges Brun-Cottan <Georges.Brun-Cottan@inria.fr>
-;; Maintainer: Stefan Monnier <monnier@gnu.org>
+;; Author: Georges Brun-Cottan <Georges.Brun-Cottan@inria.fr>
+;; Maintainer: Stefan Monnier <monnier@gnu.org>
+
+;; Keywords: extensions lisp
;; This file is part of GNU Emacs.
BODY contains code that will be executed each time the mode is (dis)activated.
It will be executed after any toggling but before running the hooks.
- BODY can start with a list of CL-style keys specifying additional arguments.
- The following keyword arguments are supported:
-:group Followed by the group name to use for any generated `defcustom'.
-:global If non-nil specifies that the minor mode is not meant to be
- buffer-local. By default, the variable is made buffer-local.
-:init-value Same as the INIT-VALUE argument.
-:lighter Same as the LIGHTER argument."
+ Before the actual body code, you can write
+ keyword arguments (alternating keywords and values).
+ These following keyword arguments are supported (other keywords
+ will be passed to `defcustom' if the minor mode is global):
+:group GROUP Custom group name to use in all generated `defcustom' forms.
+: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 Same as the INIT-VALUE argument.
+:lighter SPEC Same as the LIGHTER argument.
+:keymap MAP Same as the KEYMAP argument.
+:require SYM Same as in `defcustom'.
+
+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...)"
+ (declare (debug (&define name stringp
+ [&optional [¬ keywordp] sexp
+ &optional [¬ keywordp] sexp
+ &optional [¬ keywordp] sexp]
+ [&rest [keywordp sexp]]
+ def-body)))
+
;; Allow skipping the first three args.
(cond
((keywordp init-value)
(let* ((mode-name (symbol-name mode))
(pretty-name (easy-mmode-pretty-mode-name mode lighter))
(globalp nil)
- (togglep t) ;why would you ever want to toggle?
(group nil)
(extra-args nil)
- (keymap-sym (if (and keymap (symbolp keymap)) keymap
- (intern (concat mode-name "-map"))))
+ (extra-keywords nil)
+ (require t)
(hook (intern (concat mode-name "-hook")))
(hook-on (intern (concat mode-name "-on-hook")))
- (hook-off (intern (concat mode-name "-off-hook"))))
+ (hook-off (intern (concat mode-name "-off-hook")))
+ keyw keymap-sym)
;; Check keys.
- (while (keywordp (car body))
- (case (pop body)
+ (while (keywordp (setq keyw (car body)))
+ (setq body (cdr body))
+ (case keyw
(:init-value (setq init-value (pop body)))
(:lighter (setq lighter (pop body)))
(:global (setq globalp (pop body)))
(:extra-args (setq extra-args (pop body)))
(:group (setq group (nconc group (list :group (pop body)))))
- (t (pop body))))
+ (:require (setq require (pop body)))
+ (:keymap (setq keymap (pop body)))
+ (t (push keyw extra-keywords) (push (pop body) extra-keywords))))
+
+ (setq keymap-sym (if (and keymap (symbolp keymap)) keymap
+ (intern (concat mode-name "-map"))))
(unless group
;; We might as well provide a best-guess default group.
(setq group
- `(:group ',(intern (replace-regexp-in-string "-mode\\'" ""
- mode-name)))))
- ;; Add default properties to LIGHTER.
- (unless (or (not (stringp lighter)) (get-text-property 0 'local-map lighter)
- (get-text-property 0 'keymap lighter))
- (setq lighter
- (apply 'propertize lighter
- 'local-map (make-mode-line-mouse2-map mode)
- (unless (get-text-property 0 'help-echo lighter)
- (list 'help-echo
- (format "mouse-2: turn off %s" pretty-name))))))
+ `(:group ',(or (custom-current-group)
+ (intern (replace-regexp-in-string
+ "-mode\\'" "" mode-name))))))
`(progn
;; Define the variable to enable or disable the mode.
,(if (not globalp)
`(progn
(defvar ,mode ,init-value ,(format "Non-nil if %s is enabled.
-Use the function `%s' to change this variable." pretty-name mode))
+Use the command `%s' to change this variable." pretty-name mode))
(make-variable-buffer-local ',mode))
(let ((curfile (or (and (boundp 'byte-compile-current-file)
byte-compile-current-file)
load-file-name)))
`(defcustom ,mode ,init-value
- ,(format "Toggle %s on or off.
+ ,(format "Non-nil if %s is enabled.
See the command `%s' for a description of this minor-mode.
Setting this variable directly does not take effect;
use either \\[customize] or the function `%s'."
pretty-name mode mode)
- :set (lambda (symbol value) (funcall symbol (or value 0)))
+ :set 'custom-set-minor-mode
:initialize 'custom-initialize-default
,@group
:type 'boolean
- ,@(when curfile
- (list
- :require
- (list 'quote
- (intern (file-name-nondirectory
- (file-name-sans-extension curfile)))))))))
+ ,@(cond
+ ((not (and curfile require)) nil)
+ ((not (eq require t)) `(:require ,require))
+ (t `(:require
+ ',(intern (file-name-nondirectory
+ (file-name-sans-extension curfile))))))
+ ,@(nreverse extra-keywords))))
;; The actual function.
(defun ,mode (&optional arg ,@extra-args)
,(or doc
(format (concat "Toggle %s on or off.
Interactively, with no prefix argument, toggle the mode.
-With universal prefix ARG " (unless togglep "(or if ARG is nil) ") "turn mode on.
+With universal prefix ARG turn mode on.
With zero or negative ARG turn mode off.
\\{%s}") pretty-name keymap-sym))
- (interactive (list (or current-prefix-arg (if ,mode 0 1))))
+ ;; Use `toggle' rather than (if ,mode 0 1) so that using
+ ;; repeat-command still does the toggling correctly.
+ (interactive (list (or current-prefix-arg 'toggle)))
(setq ,mode
- (if arg
- (> (prefix-numeric-value arg) 0)
- ,(if togglep `(not ,mode) t)))
+ (cond
+ ((eq arg 'toggle) (not ,mode))
+ (arg (> (prefix-numeric-value arg) 0))
+ (t
+ (if (null ,mode) t
+ (message
+ "Toggling %s off; better pass an explicit argument."
+ ',mode)
+ nil))))
,@body
;; The on/off hooks are here for backward compatibility only.
(run-hooks ',hook (if ,mode ',hook-on ',hook-off))
- ;; Return the new setting.
(if (interactive-p)
- (message ,(format "%s %%sabled" pretty-name)
- (if ,mode "en" "dis")))
+ (progn
+ ,(if globalp `(customize-mark-as-set ',mode))
+ (unless (current-message)
+ (message ,(format "%s %%sabled" pretty-name)
+ (if ,mode "en" "dis")))))
(force-mode-line-update)
+ ;; Return the new setting.
,mode)
;; Autoloading an easy-mmode-define-minor-mode autoloads
;; The toggle's hook.
(defcustom ,hook nil
,(format "Hook run at the end of function `%s'." mode-name)
- :group ,(cadr group)
+ ,@group
:type 'hook)
;; Define the minor-mode keymap.
,(if keymap keymap-sym
`(if (boundp ',keymap-sym)
(symbol-value ',keymap-sym))))
-
+
;; If the mode is global, call the function according to the default.
- ,(if globalp `(if ,mode (,mode 1))))))
+ ,(if globalp
+ `(if (and load-file-name (not (equal ,init-value ,mode)))
+ (eval-after-load load-file-name '(,mode (if ,mode 1 -1))))))))
\f
;;;
;;; make global minor mode
(unless group
;; We might as well provide a best-guess default group.
(setq group
- `(:group ',(intern (replace-regexp-in-string "-mode\\'" ""
- (symbol-name mode))))))
+ `(:group ',(or (custom-current-group)
+ (intern (replace-regexp-in-string
+ "-mode\\'" "" (symbol-name mode)))))))
+
`(progn
;; The actual global minor-mode
(define-minor-mode ,global-mode
;; Setup hook to handle future mode changes and new buffers.
(if ,global-mode
(progn
- (add-hook 'find-file-hooks ',buffers)
+ (add-hook 'find-file-hook ',buffers)
(add-hook 'change-major-mode-hook ',cmmh))
- (remove-hook 'find-file-hooks ',buffers)
+ (remove-hook 'find-file-hook ',buffers)
(remove-hook 'change-major-mode-hook ',cmmh))
;; Go through existing buffers.
(let ((buf (pop ,buffers)))
(when (buffer-live-p buf)
(with-current-buffer buf (,turn-on))))))
+ (put ',buffers 'definition-name ',global-mode)
;; The function that catches kill-all-local-variables.
(defun ,cmmh ()
(add-to-list ',buffers (current-buffer))
- (add-hook 'post-command-hook ',buffers)))))
+ (add-hook 'post-command-hook ',buffers))
+ (put ',cmmh 'definition-name ',global-mode))))
;;;
;;; easy-mmode-defmap
Optional NAME is passed to `make-sparse-keymap'.
Optional map M can be used to modify an existing map.
ARGS is a list of additional keyword arguments."
- (let (inherit dense suppress)
+ (let (inherit dense)
(while args
(let ((key (pop args))
(val (pop args)))
(:dense (setq dense val))
(:inherit (setq inherit val))
(:group)
- ;;((eq key :suppress) (setq suppress val))
(t (message "Unknown argument %s in defmap" key)))))
(unless (keymapp m)
(setq bs (append m bs))
;;;###autoload
(defmacro easy-mmode-defsyntax (st css doc &rest args)
"Define variable ST as a syntax-table.
-CSS contains a list of syntax specifications of the form (CHAR . SYNTAX).
-"
+CSS contains a list of syntax specifications of the form (CHAR . SYNTAX)."
`(progn
(autoload 'easy-mmode-define-syntax "easy-mmode")
- (defconst ,st (easy-mmode-define-syntax ,css ,(cons 'list args)) doc)))
+ (defconst ,st (easy-mmode-define-syntax ,css ,(cons 'list args)) ,doc)))
-\f
-;;;
-;;; A "macro-only" reimplementation of define-derived-mode.
-;;;
-
-;;;###autoload
-(defmacro define-derived-mode (child parent name &optional docstring &rest body)
- "Create a new mode as a variant of an existing mode.
-
-The arguments to this command are as follow:
-
-CHILD: the name of the command for the derived mode.
-PARENT: the name of the command for the parent mode (e.g. `text-mode').
-NAME: a string which will appear in the status line (e.g. \"Hypertext\")
-DOCSTRING: an optional documentation string--if you do not supply one,
- the function will attempt to invent something useful.
-BODY: forms to execute just before running the
- hooks for the new mode.
-
-Here is how you could define LaTeX-Thesis mode as a variant of LaTeX mode:
-
- (define-derived-mode LaTeX-thesis-mode LaTeX-mode \"LaTeX-Thesis\")
-
-You could then make new key bindings for `LaTeX-thesis-mode-map'
-without changing regular LaTeX mode. In this example, BODY is empty,
-and DOCSTRING is generated by default.
-
-On a more complicated level, the following command uses `sgml-mode' as
-the parent, and then sets the variable `case-fold-search' to nil:
-
- (define-derived-mode article-mode sgml-mode \"Article\"
- \"Major mode for editing technical articles.\"
- (setq case-fold-search nil))
-
-Note that if the documentation string had been left out, it would have
-been generated automatically, with a reference to the keymap."
-
- (let* ((child-name (symbol-name child))
- (map (intern (concat child-name "-map")))
- (syntax (intern (concat child-name "-syntax-table")))
- (abbrev (intern (concat child-name "-abbrev-table")))
- (hook (intern (concat child-name "-hook"))))
-
- (unless parent (setq parent 'fundamental-mode))
-
- (when (and docstring (not (stringp docstring)))
- ;; DOCSTRING is really the first command and there's no docstring
- (push docstring body)
- (setq docstring nil))
-
- (unless (stringp docstring)
- ;; Use a default docstring.
- (setq docstring
- (format "Major mode derived from `%s' by `define-derived-mode'.
-Inherits all of the parent's attributes, but has its own keymap,
-abbrev table and syntax table:
-
- `%s', `%s' and `%s'
-
-which more-or-less shadow %s's corresponding tables."
- parent map syntax abbrev parent)))
-
- (unless (string-match (regexp-quote (symbol-name hook)) docstring)
- ;; Make sure the docstring mentions the mode's hook
- (setq docstring
- (concat docstring
- (if (eq parent 'fundamental-mode)
- "\n\nThis mode "
- (concat
- "\n\nIn addition to any hooks its parent mode "
- (if (string-match (regexp-quote (format "`%s'" parent))
- docstring) nil
- (format "`%s' " parent))
- "might have run,\nthis mode "))
- (format "runs the hook `%s'" hook)
- ", as the final step\nduring initialization.")))
-
- (unless (string-match "\\\\[{[]" docstring)
- ;; And don't forget to put the mode's keymap
- (setq docstring (concat docstring "\n\n\\{" (symbol-name map) "}")))
-
- `(progn
- (defvar ,map (make-sparse-keymap))
- (defvar ,syntax (make-char-table 'syntax-table nil))
- (defvar ,abbrev)
- (define-abbrev-table ',abbrev nil)
- (put ',child 'derived-mode-parent ',parent)
-
- (defun ,child ()
- ,docstring
- (interactive)
- ; Run the parent.
- (combine-run-hooks
-
- (,parent)
- ; Identify special modes.
- (put ',child 'special (get ',parent 'special))
- ; Identify the child mode.
- (setq major-mode ',child)
- (setq mode-name ,name)
- ; Set up maps and tables.
- (unless (keymap-parent ,map)
- (set-keymap-parent ,map (current-local-map)))
- (let ((parent (char-table-parent ,syntax)))
- (unless (and parent (not (eq parent (standard-syntax-table))))
- (set-char-table-parent ,syntax (syntax-table))))
- (when local-abbrev-table
- (mapatoms
- (lambda (symbol)
- (or (intern-soft (symbol-name symbol) ,abbrev)
- (define-abbrev ,abbrev (symbol-name symbol)
- (symbol-value symbol) (symbol-function symbol))))
- local-abbrev-table))
-
- (use-local-map ,map)
- (set-syntax-table ,syntax)
- (setq local-abbrev-table ,abbrev)
- ; Splice in the body (if any).
- ,@body)
- ; Run the hooks, if any.
- (run-hooks ',hook)))))
-
-;; Inspired from derived-mode-class in derived.el
-(defun easy-mmode-derived-mode-p (mode)
- "Non-nil if the current major mode is derived from MODE.
-Uses the `derived-mode-parent' property of the symbol to trace backwards."
- (let ((parent major-mode))
- (while (and (not (eq parent mode))
- (setq parent (get parent 'derived-mode-parent))))
- parent))
-
\f
;;;
;;; easy-mmode-define-navigation
;;;
-(defmacro easy-mmode-define-navigation (base re &optional name endfun)
+(defmacro easy-mmode-define-navigation (base re &optional name endfun narrowfun)
"Define BASE-next and BASE-prev to navigate in the buffer.
RE determines the places the commands should move point to.
NAME should describe the entities matched by RE. It is used to build
BASE-next also tries to make sure that the whole entry is visible by
searching for its end (by calling ENDFUN if provided or by looking for
the next entry) and recentering if necessary.
-ENDFUN should return the end position (with or without moving point)."
+ENDFUN should return the end position (with or without moving point).
+NARROWFUN non-nil means to check for narrowing before moving, and if
+found, do widen first and then call NARROWFUN with no args after moving."
(let* ((base-name (symbol-name base))
(prev-sym (intern (concat base-name "-prev")))
- (next-sym (intern (concat base-name "-next"))))
- (unless name (setq name (symbol-name base-name)))
+ (next-sym (intern (concat base-name "-next")))
+ (check-narrow-maybe
+ (when narrowfun
+ '(setq was-narrowed
+ (prog1 (or (< (- (point-max) (point-min)) (buffer-size)))
+ (widen)))))
+ (re-narrow-maybe (when narrowfun
+ `(when was-narrowed (,narrowfun)))))
+ (unless name (setq name base-name))
`(progn
(add-to-list 'debug-ignored-errors
,(concat "^No \\(previous\\|next\\) " (regexp-quote name)))
(interactive)
(unless count (setq count 1))
(if (< count 0) (,prev-sym (- count))
- (if (looking-at ,re) (incf count))
- (if (not (re-search-forward ,re nil t count))
- (if (looking-at ,re)
- (goto-char (or ,(if endfun `(,endfun)) (point-max)))
- (error ,(format "No next %s" name)))
- (goto-char (match-beginning 0))
- (when (eq (current-buffer) (window-buffer (selected-window)))
- (let ((endpt (or (save-excursion
- ,(if endfun `(,endfun)
- `(re-search-forward ,re nil t 2)))
- (point-max))))
- (unless (pos-visible-in-window-p endpt nil t)
- (recenter '(0))))))))
+ (if (looking-at ,re) (setq count (1+ count)))
+ (let (was-narrowed)
+ ,check-narrow-maybe
+ (if (not (re-search-forward ,re nil t count))
+ (if (looking-at ,re)
+ (goto-char (or ,(if endfun `(,endfun)) (point-max)))
+ (error "No next %s" ,name))
+ (goto-char (match-beginning 0))
+ (when (and (eq (current-buffer) (window-buffer (selected-window)))
+ (interactive-p))
+ (let ((endpt (or (save-excursion
+ ,(if endfun `(,endfun)
+ `(re-search-forward ,re nil t 2)))
+ (point-max))))
+ (unless (pos-visible-in-window-p endpt nil t)
+ (recenter '(0))))))
+ ,re-narrow-maybe)))
(defun ,prev-sym (&optional count)
,(format "Go to the previous COUNT'th %s" (or name base-name))
(interactive)
(unless count (setq count 1))
(if (< count 0) (,next-sym (- count))
- (unless (re-search-backward ,re nil t count)
- (error ,(format "No previous %s" name))))))))
+ (let (was-narrowed)
+ ,check-narrow-maybe
+ (unless (re-search-backward ,re nil t count)
+ (error "No previous %s" ,name))
+ ,re-narrow-maybe))))))
+
(provide 'easy-mmode)
+;;; arch-tag: d48a5250-6961-4528-9cb0-3c9ea042a66a
;;; easy-mmode.el ends here