(grep-compute-defaults): Set the standard-value
[bpt/emacs.git] / lisp / emacs-lisp / easy-mmode.el
CommitLineData
e8af40ee 1;;; easy-mmode.el --- easy definition for major and minor modes
6b279740 2
ed771c89 3;; Copyright (C) 1997, 2000, 2001 Free Software Foundation, Inc.
6b279740 4
9781053a
PJ
5;; Author: Georges Brun-Cottan <Georges.Brun-Cottan@inria.fr>
6;; Maintainer: Stefan Monnier <monnier@gnu.org>
7
8;; Keywords: extensions lisp
6b279740
RS
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation; either version 2, or (at your option)
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs; see the file COPYING. If not, write to the
24;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25;; Boston, MA 02111-1307, USA.
26
27;;; Commentary:
28
29;; Minor modes are useful and common. This package makes defining a
30;; minor mode easy, by focusing on the writing of the minor mode
31;; functionalities themselves. Moreover, this package enforces a
32;; conventional naming of user interface primitives, making things
33;; natural for the minor-mode end-users.
34
35;; For each mode, easy-mmode defines the following:
36;; <mode> : The minor mode predicate. A buffer-local variable.
37;; <mode>-map : The keymap possibly associated to <mode>.
c8c21615
SM
38;; <mode>-hook : The hook run at the end of the toggle function.
39;; see `define-minor-mode' documentation
6b279740
RS
40;;
41;; eval
c8c21615 42;; (pp (macroexpand '(define-minor-mode <your-mode> <doc>)))
6b279740
RS
43;; to check the result before using it.
44
45;; The order in which minor modes are installed is important. Keymap
46;; lookup proceeds down minor-mode-map-alist, and the order there
47;; tends to be the reverse of the order in which the modes were
48;; installed. Perhaps there should be a feature to let you specify
49;; orderings.
50
5a7a545c
SM
51;; Additionally to `define-minor-mode', the package provides convenient
52;; ways to define keymaps, and other helper functions for major and minor modes.
6b279740 53
5a7a545c 54;;; Code:
6b279740 55
be22f4cc
SM
56(eval-when-compile (require 'cl))
57
b5bbbb76
SM
58(defun easy-mmode-pretty-mode-name (mode &optional lighter)
59 "Turn the symbol MODE into a string intended for the user.
60If provided LIGHTER will be used to help choose capitalization."
61 (let* ((case-fold-search t)
b643ec53
SM
62 (name (concat (replace-regexp-in-string
63 "-Minor" " minor"
64 (capitalize (replace-regexp-in-string
65 "-mode\\'" "" (symbol-name mode))))
b5bbbb76
SM
66 " mode")))
67 (if (not (stringp lighter)) name
68 (setq lighter (replace-regexp-in-string "\\`\\s-+\\|\\-s+\\'" "" lighter))
69 (replace-regexp-in-string lighter lighter name t t))))
3837de12 70
6b279740 71;;;###autoload
29cc3b84
SM
72(defalias 'easy-mmode-define-minor-mode 'define-minor-mode)
73;;;###autoload
74(defmacro define-minor-mode (mode doc &optional init-value lighter keymap &rest body)
6b279740 75 "Define a new minor mode MODE.
b5bbbb76 76This function defines the associated control variable MODE, keymap MODE-map,
bff53411 77toggle command MODE, and hook MODE-hook.
6b279740
RS
78
79DOC is the documentation for the mode toggle command.
29cc3b84 80Optional INIT-VALUE is the initial value of the mode's variable.
c8c21615 81Optional LIGHTER is displayed in the modeline when the mode is on.
6b279740 82Optional KEYMAP is the default (defvar) keymap bound to the mode keymap.
b5bbbb76 83 If it is a list, it is passed to `easy-mmode-define-keymap'
bff53411
SM
84 in order to build a valid keymap. It's generally better to use
85 a separate MODE-map variable than to use this argument.
86The above three arguments can be skipped if keyword arguments are
87used (see below).
88
29cc3b84 89BODY contains code that will be executed each time the mode is (dis)activated.
b5bbbb76
SM
90 It will be executed after any toggling but before running the hooks.
91 BODY can start with a list of CL-style keys specifying additional arguments.
bff53411 92 The following keyword arguments are supported:
c8fb3bf9
SM
93:group GROUP Group name to use for any generated `defcustom'.
94:global GLOBAL If non-nil specifies that the minor mode is not meant to be
95 buffer-local. By default, the variable is made buffer-local.
96:init-value VAL Same as the INIT-VALUE argument.
97:lighter SPEC Same as the LIGHTER argument.
98:require SYM Same as defcustom's :require argument."
bff53411
SM
99 ;; Allow skipping the first three args.
100 (cond
101 ((keywordp init-value)
102 (setq body (list* init-value lighter keymap body)
103 init-value nil lighter nil keymap nil))
104 ((keywordp lighter)
105 (setq body (list* lighter keymap body) lighter nil keymap nil))
106 ((keywordp keymap) (push keymap body) (setq keymap nil)))
107
6b279740 108 (let* ((mode-name (symbol-name mode))
b5bbbb76 109 (pretty-name (easy-mmode-pretty-mode-name mode lighter))
c8c21615 110 (globalp nil)
0a74e3bf
SM
111 (group nil)
112 (extra-args nil)
c8fb3bf9 113 (require t)
1328a6df
SM
114 (keymap-sym (if (and keymap (symbolp keymap)) keymap
115 (intern (concat mode-name "-map"))))
b5bbbb76
SM
116 (hook (intern (concat mode-name "-hook")))
117 (hook-on (intern (concat mode-name "-on-hook")))
118 (hook-off (intern (concat mode-name "-off-hook"))))
119
b5bbbb76 120 ;; Check keys.
be22f4cc
SM
121 (while (keywordp (car body))
122 (case (pop body)
bff53411
SM
123 (:init-value (setq init-value (pop body)))
124 (:lighter (setq lighter (pop body)))
be22f4cc 125 (:global (setq globalp (pop body)))
0a74e3bf
SM
126 (:extra-args (setq extra-args (pop body)))
127 (:group (setq group (nconc group (list :group (pop body)))))
c8fb3bf9 128 (:require (setq require (pop body)))
bff53411 129 (t (pop body))))
eab6e8b9 130
0a74e3bf
SM
131 (unless group
132 ;; We might as well provide a best-guess default group.
133 (setq group
134 `(:group ',(intern (replace-regexp-in-string "-mode\\'" ""
135 mode-name)))))
b5bbbb76 136
6b279740 137 `(progn
5e21ef7a 138 ;; Define the variable to enable or disable the mode.
d5b037c5
SM
139 ,(if (not globalp)
140 `(progn
141 (defvar ,mode ,init-value ,(format "Non-nil if %s is enabled.
a2ed9670 142Use the command `%s' to change this variable." pretty-name mode))
d5b037c5 143 (make-variable-buffer-local ',mode))
6b279740 144
1328a6df
SM
145 (let ((curfile (or (and (boundp 'byte-compile-current-file)
146 byte-compile-current-file)
147 load-file-name)))
148 `(defcustom ,mode ,init-value
a2ed9670 149 ,(format "Non-nil if %s is enabled.
bff53411 150See the command `%s' for a description of this minor-mode.
d5b037c5
SM
151Setting this variable directly does not take effect;
152use either \\[customize] or the function `%s'."
bff53411 153 pretty-name mode mode)
1328a6df
SM
154 :set (lambda (symbol value) (funcall symbol (or value 0)))
155 :initialize 'custom-initialize-default
0a74e3bf 156 ,@group
1328a6df 157 :type 'boolean
c8fb3bf9
SM
158 ,@(cond
159 ((not (and curfile require)) nil)
160 ((not (eq require t)) `(:require ,require))
161 (t `(:require
162 ',(intern (file-name-nondirectory
163 (file-name-sans-extension curfile)))))))))
1328a6df 164
b5bbbb76 165 ;; The actual function.
0a74e3bf 166 (defun ,mode (&optional arg ,@extra-args)
b5bbbb76 167 ,(or doc
bff53411
SM
168 (format (concat "Toggle %s on or off.
169Interactively, with no prefix argument, toggle the mode.
5ddfa187 170With universal prefix ARG turn mode on.
b5bbbb76 171With zero or negative ARG turn mode off.
bff53411 172\\{%s}") pretty-name keymap-sym))
5ddfa187
SM
173 ;; Use `toggle' rather than (if ,mode 0 1) so that using
174 ;; repeat-command still does the toggling correctly.
175 (interactive (list (or current-prefix-arg 'toggle)))
b5bbbb76 176 (setq ,mode
5ddfa187
SM
177 (cond
178 ((eq arg 'toggle) (not ,mode))
179 (arg (> (prefix-numeric-value arg) 0))
180 (t
181 (if (null ,mode) t
182 (message
183 "Toggling %s off; better pass an explicit argument."
184 ',mode)
185 nil))))
b5bbbb76
SM
186 ,@body
187 ;; The on/off hooks are here for backward compatibility only.
188 (run-hooks ',hook (if ,mode ',hook-on ',hook-off))
b5bbbb76 189 (if (interactive-p)
d99d3266
SM
190 (progn
191 ,(if globalp `(customize-mark-as-set ',mode))
192 (message ,(format "%s %%sabled" pretty-name)
193 (if ,mode "en" "dis"))))
bff53411 194 (force-mode-line-update)
d99d3266 195 ;; Return the new setting.
b5bbbb76 196 ,mode)
6b279740 197
1328a6df
SM
198 ;; Autoloading an easy-mmode-define-minor-mode autoloads
199 ;; everything up-to-here.
200 :autoload-end
201
0a74e3bf
SM
202 ;; The toggle's hook.
203 (defcustom ,hook nil
204 ,(format "Hook run at the end of function `%s'." mode-name)
205 :group ,(cadr group)
206 :type 'hook)
207
d5b037c5 208 ;; Define the minor-mode keymap.
1328a6df 209 ,(unless (symbolp keymap) ;nil is also a symbol.
d5b037c5 210 `(defvar ,keymap-sym
1328a6df
SM
211 (let ((m ,keymap))
212 (cond ((keymapp m) m)
213 ((listp m) (easy-mmode-define-keymap m))
214 (t (error "Invalid keymap %S" ,keymap))))
d5b037c5
SM
215 ,(format "Keymap for `%s'." mode-name)))
216
b5bbbb76 217 (add-minor-mode ',mode ',lighter
1328a6df 218 ,(if keymap keymap-sym
cb5da1a3
SM
219 `(if (boundp ',keymap-sym)
220 (symbol-value ',keymap-sym))))
bff53411 221
c8c21615 222 ;; If the mode is global, call the function according to the default.
bb76239b
SM
223 ,(if globalp
224 `(if (and load-file-name (not (equal ,init-value ,mode)))
225 (eval-after-load load-file-name '(,mode (if ,mode 1 -1))))))))
5a7a545c 226\f
be22f4cc
SM
227;;;
228;;; make global minor mode
229;;;
230
d5b037c5 231;;;###autoload
be22f4cc
SM
232(defmacro easy-mmode-define-global-mode (global-mode mode turn-on
233 &rest keys)
bff53411 234 "Make GLOBAL-MODE out of the buffer-local minor MODE.
be22f4cc
SM
235TURN-ON is a function that will be called with no args in every buffer
236 and that should try to turn MODE on if applicable for that buffer.
237KEYS is a list of CL-style keyword arguments:
238:group to specify the custom group."
0a74e3bf 239 (let* ((global-mode-name (symbol-name global-mode))
be22f4cc
SM
240 (pretty-name (easy-mmode-pretty-mode-name mode))
241 (pretty-global-name (easy-mmode-pretty-mode-name global-mode))
0a74e3bf
SM
242 (group nil)
243 (extra-args nil)
be22f4cc
SM
244 (buffers (intern (concat global-mode-name "-buffers")))
245 (cmmh (intern (concat global-mode-name "-cmmh"))))
246
247 ;; Check keys.
248 (while (keywordp (car keys))
249 (case (pop keys)
0a74e3bf
SM
250 (:extra-args (setq extra-args (pop keys)))
251 (:group (setq group (nconc group (list :group (pop keys)))))
be22f4cc
SM
252 (t (setq keys (cdr keys)))))
253
0a74e3bf
SM
254 (unless group
255 ;; We might as well provide a best-guess default group.
256 (setq group
257 `(:group ',(intern (replace-regexp-in-string "-mode\\'" ""
258 (symbol-name mode))))))
be22f4cc 259 `(progn
be22f4cc
SM
260 ;; The actual global minor-mode
261 (define-minor-mode ,global-mode
262 ,(format "Toggle %s in every buffer.
263With prefix ARG, turn %s on if and only if ARG is positive.
264%s is actually not turned on in every buffer but only in those
265in which `%s' turns it on."
266 pretty-name pretty-global-name pretty-name turn-on)
0a74e3bf 267 :global t :extra-args ,extra-args ,@group
be22f4cc
SM
268
269 ;; Setup hook to handle future mode changes and new buffers.
270 (if ,global-mode
d5b037c5 271 (progn
5ddfa187 272 (add-hook 'find-file-hook ',buffers)
d5b037c5 273 (add-hook 'change-major-mode-hook ',cmmh))
5ddfa187 274 (remove-hook 'find-file-hook ',buffers)
be22f4cc
SM
275 (remove-hook 'change-major-mode-hook ',cmmh))
276
277 ;; Go through existing buffers.
278 (dolist (buf (buffer-list))
279 (with-current-buffer buf
34befa9a 280 (if ,global-mode (,turn-on) (when ,mode (,mode -1))))))
be22f4cc 281
1328a6df
SM
282 ;; Autoloading easy-mmode-define-global-mode
283 ;; autoloads everything up-to-here.
284 :autoload-end
285
be22f4cc
SM
286 ;; List of buffers left to process.
287 (defvar ,buffers nil)
288
289 ;; The function that calls TURN-ON in each buffer.
290 (defun ,buffers ()
be22f4cc 291 (remove-hook 'post-command-hook ',buffers)
d5b037c5
SM
292 (while ,buffers
293 (let ((buf (pop ,buffers)))
294 (when (buffer-live-p buf)
295 (with-current-buffer buf (,turn-on))))))
02565504 296 (put ',buffers 'definition-name ',global-mode)
be22f4cc
SM
297
298 ;; The function that catches kill-all-local-variables.
299 (defun ,cmmh ()
300 (add-to-list ',buffers (current-buffer))
02565504
RS
301 (add-hook 'post-command-hook ',buffers))
302 (put ',cmmh 'definition-name ',global-mode))))
be22f4cc 303
5a7a545c
SM
304;;;
305;;; easy-mmode-defmap
306;;;
307
308(if (fboundp 'set-keymap-parents)
309 (defalias 'easy-mmode-set-keymap-parents 'set-keymap-parents)
310 (defun easy-mmode-set-keymap-parents (m parents)
311 (set-keymap-parent
312 m
313 (cond
314 ((not (consp parents)) parents)
315 ((not (cdr parents)) (car parents))
316 (t (let ((m (copy-keymap (pop parents))))
317 (easy-mmode-set-keymap-parents m parents)
318 m))))))
319
5d78d57d 320;;;###autoload
5a7a545c
SM
321(defun easy-mmode-define-keymap (bs &optional name m args)
322 "Return a keymap built from bindings BS.
323BS must be a list of (KEY . BINDING) where
3837de12
SM
324KEY and BINDINGS are suitable for `define-key'.
325Optional NAME is passed to `make-sparse-keymap'.
326Optional map M can be used to modify an existing map.
165958d2 327ARGS is a list of additional keyword arguments."
5a7a545c
SM
328 (let (inherit dense suppress)
329 (while args
330 (let ((key (pop args))
331 (val (pop args)))
be22f4cc 332 (case key
165958d2 333 (:name (setq name val))
be22f4cc
SM
334 (:dense (setq dense val))
335 (:inherit (setq inherit val))
336 (:group)
5a7a545c
SM
337 ;;((eq key :suppress) (setq suppress val))
338 (t (message "Unknown argument %s in defmap" key)))))
339 (unless (keymapp m)
340 (setq bs (append m bs))
341 (setq m (if dense (make-keymap name) (make-sparse-keymap name))))
342 (dolist (b bs)
343 (let ((keys (car b))
344 (binding (cdr b)))
345 (dolist (key (if (consp keys) keys (list keys)))
346 (cond
347 ((symbolp key)
348 (substitute-key-definition key binding m global-map))
349 ((null binding)
350 (unless (keymapp (lookup-key m key)) (define-key m key binding)))
351 ((let ((o (lookup-key m key)))
352 (or (null o) (numberp o) (eq o 'undefined)))
353 (define-key m key binding))))))
354 (cond
355 ((keymapp inherit) (set-keymap-parent m inherit))
356 ((consp inherit) (easy-mmode-set-keymap-parents m inherit)))
357 m))
358
359;;;###autoload
360(defmacro easy-mmode-defmap (m bs doc &rest args)
5d78d57d
SM
361 `(defconst ,m
362 (easy-mmode-define-keymap ,bs nil (if (boundp ',m) ,m) ,(cons 'list args))
363 ,doc))
5a7a545c
SM
364
365\f
366;;;
367;;; easy-mmode-defsyntax
368;;;
369
370(defun easy-mmode-define-syntax (css args)
e4fe3460
SM
371 (let ((st (make-syntax-table (plist-get args :copy)))
372 (parent (plist-get args :inherit)))
5a7a545c
SM
373 (dolist (cs css)
374 (let ((char (car cs))
375 (syntax (cdr cs)))
376 (if (sequencep char)
e4ad5f9e 377 (mapcar (lambda (c) (modify-syntax-entry c syntax st)) char)
5a7a545c 378 (modify-syntax-entry char syntax st))))
e4fe3460
SM
379 (if parent (set-char-table-parent
380 st (if (symbolp parent) (symbol-value parent) parent)))
5a7a545c
SM
381 st))
382
383;;;###autoload
384(defmacro easy-mmode-defsyntax (st css doc &rest args)
e4fe3460 385 "Define variable ST as a syntax-table.
2a83a11d 386CSS contains a list of syntax specifications of the form (CHAR . SYNTAX)."
e4ad5f9e
SM
387 `(progn
388 (autoload 'easy-mmode-define-syntax "easy-mmode")
2a83a11d 389 (defconst ,st (easy-mmode-define-syntax ,css ,(cons 'list args)) ,doc)))
5a7a545c
SM
390
391
392\f
c7ea3acc
SM
393;;;
394;;; easy-mmode-define-navigation
395;;;
396
397(defmacro easy-mmode-define-navigation (base re &optional name endfun)
398 "Define BASE-next and BASE-prev to navigate in the buffer.
399RE determines the places the commands should move point to.
eed083e6 400NAME should describe the entities matched by RE. It is used to build
c7ea3acc
SM
401 the docstrings of the two functions.
402BASE-next also tries to make sure that the whole entry is visible by
403 searching for its end (by calling ENDFUN if provided or by looking for
404 the next entry) and recentering if necessary.
405ENDFUN should return the end position (with or without moving point)."
406 (let* ((base-name (symbol-name base))
407 (prev-sym (intern (concat base-name "-prev")))
408 (next-sym (intern (concat base-name "-next"))))
36a5b60e 409 (unless name (setq name (symbol-name base-name)))
c7ea3acc 410 `(progn
b5bbbb76
SM
411 (add-to-list 'debug-ignored-errors
412 ,(concat "^No \\(previous\\|next\\) " (regexp-quote name)))
c7ea3acc 413 (defun ,next-sym (&optional count)
36a5b60e 414 ,(format "Go to the next COUNT'th %s." name)
c7ea3acc
SM
415 (interactive)
416 (unless count (setq count 1))
417 (if (< count 0) (,prev-sym (- count))
418 (if (looking-at ,re) (incf count))
eed083e6
SM
419 (if (not (re-search-forward ,re nil t count))
420 (if (looking-at ,re)
421 (goto-char (or ,(if endfun `(,endfun)) (point-max)))
422 (error ,(format "No next %s" name)))
423 (goto-char (match-beginning 0))
851040a5
SM
424 (when (and (eq (current-buffer) (window-buffer (selected-window)))
425 (interactive-p))
eed083e6
SM
426 (let ((endpt (or (save-excursion
427 ,(if endfun `(,endfun)
428 `(re-search-forward ,re nil t 2)))
429 (point-max))))
430 (unless (pos-visible-in-window-p endpt nil t)
431 (recenter '(0))))))))
c7ea3acc
SM
432 (defun ,prev-sym (&optional count)
433 ,(format "Go to the previous COUNT'th %s" (or name base-name))
434 (interactive)
435 (unless count (setq count 1))
436 (if (< count 0) (,next-sym (- count))
36a5b60e 437 (unless (re-search-backward ,re nil t count)
c8c21615 438 (error ,(format "No previous %s" name))))))))
5a7a545c 439
6b279740
RS
440(provide 'easy-mmode)
441
442;;; easy-mmode.el ends here