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