+ ;; 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 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 "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 'custom-set-minor-mode
+ :initialize 'custom-initialize-default
+ ,@group
+ :type 'boolean
+ ,@(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 turn mode on.
+With zero or negative ARG turn mode off.
+\\{%s}") pretty-name keymap-sym))
+ ;; 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
+ (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))
+ (if (interactive-p)
+ (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
+ ;; everything up-to-here.
+ :autoload-end
+
+ ;; The toggle's hook.
+ (defcustom ,hook nil
+ ,(format "Hook run at the end of function `%s'." mode-name)
+ ,@group
+ :type 'hook)
+
+ ;; Define the minor-mode keymap.
+ ,(unless (symbolp keymap) ;nil is also a symbol.
+ `(defvar ,keymap-sym
+ (let ((m ,keymap))
+ (cond ((keymapp m) m)
+ ((listp m) (easy-mmode-define-keymap m))
+ (t (error "Invalid keymap %S" ,keymap))))
+ ,(format "Keymap for `%s'." mode-name)))
+
+ (add-minor-mode ',mode ',lighter
+ ,(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 (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
+;;;
+
+;;;###autoload
+(defmacro easy-mmode-define-global-mode (global-mode mode turn-on
+ &rest keys)
+ "Make GLOBAL-MODE out of the buffer-local minor MODE.
+TURN-ON is a function that will be called with no args in every buffer
+ and that should try to turn MODE on if applicable for that buffer.
+KEYS is a list of CL-style keyword arguments:
+:group to specify the custom group."
+ (let* ((global-mode-name (symbol-name global-mode))
+ (pretty-name (easy-mmode-pretty-mode-name mode))
+ (pretty-global-name (easy-mmode-pretty-mode-name global-mode))
+ (group nil)
+ (extra-args nil)
+ (buffers (intern (concat global-mode-name "-buffers")))
+ (cmmh (intern (concat global-mode-name "-cmmh"))))
+
+ ;; Check keys.
+ (while (keywordp (car keys))
+ (case (pop keys)
+ (:extra-args (setq extra-args (pop keys)))
+ (:group (setq group (nconc group (list :group (pop keys)))))
+ (t (setq keys (cdr keys)))))
+
+ (unless group
+ ;; We might as well provide a best-guess default group.
+ (setq group
+ `(: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
+ ,(format "Toggle %s in every buffer.
+With prefix ARG, turn %s on if and only if ARG is positive.
+%s is actually not turned on in every buffer but only in those
+in which `%s' turns it on."
+ pretty-name pretty-global-name pretty-name turn-on)
+ :global t :extra-args ,extra-args ,@group
+
+ ;; Setup hook to handle future mode changes and new buffers.
+ (if ,global-mode
+ (progn
+ (add-hook 'find-file-hook ',buffers)
+ (add-hook 'change-major-mode-hook ',cmmh))
+ (remove-hook 'find-file-hook ',buffers)
+ (remove-hook 'change-major-mode-hook ',cmmh))
+
+ ;; Go through existing buffers.
+ (dolist (buf (buffer-list))
+ (with-current-buffer buf
+ (if ,global-mode (,turn-on) (when ,mode (,mode -1))))))
+
+ ;; Autoloading easy-mmode-define-global-mode
+ ;; autoloads everything up-to-here.
+ :autoload-end
+
+ ;; List of buffers left to process.
+ (defvar ,buffers nil)
+
+ ;; The function that calls TURN-ON in each buffer.
+ (defun ,buffers ()
+ (remove-hook 'post-command-hook ',buffers)
+ (while ,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))
+ (put ',cmmh 'definition-name ',global-mode))))
+
+;;;
+;;; easy-mmode-defmap
+;;;
+
+(if (fboundp 'set-keymap-parents)
+ (defalias 'easy-mmode-set-keymap-parents 'set-keymap-parents)
+ (defun easy-mmode-set-keymap-parents (m parents)
+ (set-keymap-parent
+ m
+ (cond
+ ((not (consp parents)) parents)
+ ((not (cdr parents)) (car parents))
+ (t (let ((m (copy-keymap (pop parents))))
+ (easy-mmode-set-keymap-parents m parents)
+ m))))))
+
+;;;###autoload
+(defun easy-mmode-define-keymap (bs &optional name m args)
+ "Return a keymap built from bindings BS.
+BS must be a list of (KEY . BINDING) where
+KEY and BINDINGS are suitable for `define-key'.
+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)
+ (while args
+ (let ((key (pop args))
+ (val (pop args)))
+ (case key
+ (:name (setq name val))
+ (:dense (setq dense val))
+ (:inherit (setq inherit val))
+ (:group)
+ (t (message "Unknown argument %s in defmap" key)))))
+ (unless (keymapp m)
+ (setq bs (append m bs))
+ (setq m (if dense (make-keymap name) (make-sparse-keymap name))))
+ (dolist (b bs)
+ (let ((keys (car b))
+ (binding (cdr b)))
+ (dolist (key (if (consp keys) keys (list keys)))
+ (cond
+ ((symbolp key)
+ (substitute-key-definition key binding m global-map))
+ ((null binding)
+ (unless (keymapp (lookup-key m key)) (define-key m key binding)))
+ ((let ((o (lookup-key m key)))
+ (or (null o) (numberp o) (eq o 'undefined)))
+ (define-key m key binding))))))
+ (cond
+ ((keymapp inherit) (set-keymap-parent m inherit))
+ ((consp inherit) (easy-mmode-set-keymap-parents m inherit)))
+ m))
+
+;;;###autoload
+(defmacro easy-mmode-defmap (m bs doc &rest args)
+ `(defconst ,m
+ (easy-mmode-define-keymap ,bs nil (if (boundp ',m) ,m) ,(cons 'list args))
+ ,doc))
+
+\f
+;;;
+;;; easy-mmode-defsyntax
+;;;
+
+(defun easy-mmode-define-syntax (css args)
+ (let ((st (make-syntax-table (plist-get args :copy)))
+ (parent (plist-get args :inherit)))
+ (dolist (cs css)
+ (let ((char (car cs))
+ (syntax (cdr cs)))
+ (if (sequencep char)
+ (mapcar (lambda (c) (modify-syntax-entry c syntax st)) char)
+ (modify-syntax-entry char syntax st))))
+ (if parent (set-char-table-parent
+ st (if (symbolp parent) (symbol-value parent) parent)))
+ st))
+
+;;;###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)."
+ `(progn
+ (autoload 'easy-mmode-define-syntax "easy-mmode")
+ (defconst ,st (easy-mmode-define-syntax ,css ,(cons 'list args)) ,doc)))
+
+
+\f
+;;;
+;;; easy-mmode-define-navigation
+;;;
+
+(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
+ the docstrings of the two functions.
+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).
+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")))
+ (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)))
+ (defun ,next-sym (&optional count)
+ ,(format "Go to the next COUNT'th %s." name)
+ (interactive)
+ (unless count (setq count 1))
+ (if (< count 0) (,prev-sym (- count))
+ (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))
+ (let (was-narrowed)
+ ,check-narrow-maybe
+ (unless (re-search-backward ,re nil t count)
+ (error "No previous %s" ,name))
+ ,re-narrow-maybe))))))
+