Sync to HEAD
[bpt/emacs.git] / lisp / emacs-lisp / easy-mmode.el
CommitLineData
e8af40ee 1;;; easy-mmode.el --- easy definition for major and minor modes
6b279740 2
6b61353c 3;; Copyright (C) 1997,2000,01,02,03,2004 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 90 It will be executed after any toggling but before running the hooks.
a6ce6869
RS
91 Before the actual body code, you can write
92 keyword arguments (alternating keywords and values).
73ceba9f
SM
93 These following keyword arguments are supported (other keywords
94 will be passed to `defcustom' if the minor mode is global):
a6ce6869 95:group GROUP Custom group name to use in all generated `defcustom' forms.
c8fb3bf9 96:global GLOBAL If non-nil specifies that the minor mode is not meant to be
a6ce6869
RS
97 buffer-local, so don't make the variable MODE buffer-local.
98 By default, the mode is buffer-local.
c8fb3bf9
SM
99:init-value VAL Same as the INIT-VALUE argument.
100:lighter SPEC Same as the LIGHTER argument.
2e2a0075 101:keymap MAP Same as the KEYMAP argument.
a6ce6869
RS
102:require SYM Same as in `defcustom'.
103
104For example, you could write
105 (define-minor-mode foo-mode \"If enabled, foo on you!\"
73ceba9f 106 :lighter \" Foo\" :require 'foo :global t :group 'hassle :version \"27.5\"
a6ce6869 107 ...BODY CODE...)"
2e2a0075
SM
108 (declare (debug (&define name stringp
109 [&optional [&not keywordp] sexp
110 &optional [&not keywordp] sexp
111 &optional [&not keywordp] sexp]
112 [&rest [keywordp sexp]]
113 def-body)))
a6ce6869 114
bff53411
SM
115 ;; Allow skipping the first three args.
116 (cond
117 ((keywordp init-value)
118 (setq body (list* init-value lighter keymap body)
119 init-value nil lighter nil keymap nil))
120 ((keywordp lighter)
121 (setq body (list* lighter keymap body) lighter nil keymap nil))
122 ((keywordp keymap) (push keymap body) (setq keymap nil)))
123
6b279740 124 (let* ((mode-name (symbol-name mode))
b5bbbb76 125 (pretty-name (easy-mmode-pretty-mode-name mode lighter))
c8c21615 126 (globalp nil)
0a74e3bf
SM
127 (group nil)
128 (extra-args nil)
73ceba9f 129 (extra-keywords nil)
c8fb3bf9 130 (require t)
b5bbbb76
SM
131 (hook (intern (concat mode-name "-hook")))
132 (hook-on (intern (concat mode-name "-on-hook")))
73ceba9f 133 (hook-off (intern (concat mode-name "-off-hook")))
2e2a0075 134 keyw keymap-sym)
b5bbbb76 135
b5bbbb76 136 ;; Check keys.
73ceba9f
SM
137 (while (keywordp (setq keyw (car body)))
138 (setq body (cdr body))
139 (case keyw
bff53411
SM
140 (:init-value (setq init-value (pop body)))
141 (:lighter (setq lighter (pop body)))
be22f4cc 142 (:global (setq globalp (pop body)))
0a74e3bf
SM
143 (:extra-args (setq extra-args (pop body)))
144 (:group (setq group (nconc group (list :group (pop body)))))
c8fb3bf9 145 (:require (setq require (pop body)))
2e2a0075 146 (:keymap (setq keymap (pop body)))
73ceba9f 147 (t (push keyw extra-keywords) (push (pop body) extra-keywords))))
eab6e8b9 148
2e2a0075
SM
149 (setq keymap-sym (if (and keymap (symbolp keymap)) keymap
150 (intern (concat mode-name "-map"))))
151
0a74e3bf
SM
152 (unless group
153 ;; We might as well provide a best-guess default group.
154 (setq group
dce88ea6
MR
155 `(:group ',(or (custom-current-group)
156 (intern (replace-regexp-in-string
157 "-mode\\'" "" mode-name))))))
b5bbbb76 158
6b279740 159 `(progn
5e21ef7a 160 ;; Define the variable to enable or disable the mode.
d5b037c5
SM
161 ,(if (not globalp)
162 `(progn
163 (defvar ,mode ,init-value ,(format "Non-nil if %s is enabled.
a2ed9670 164Use the command `%s' to change this variable." pretty-name mode))
d5b037c5 165 (make-variable-buffer-local ',mode))
6b279740 166
1328a6df
SM
167 (let ((curfile (or (and (boundp 'byte-compile-current-file)
168 byte-compile-current-file)
169 load-file-name)))
170 `(defcustom ,mode ,init-value
a2ed9670 171 ,(format "Non-nil if %s is enabled.
bff53411 172See the command `%s' for a description of this minor-mode.
d5b037c5
SM
173Setting this variable directly does not take effect;
174use either \\[customize] or the function `%s'."
bff53411 175 pretty-name mode mode)
73ceba9f 176 :set 'custom-set-minor-mode
1328a6df 177 :initialize 'custom-initialize-default
0a74e3bf 178 ,@group
1328a6df 179 :type 'boolean
c8fb3bf9
SM
180 ,@(cond
181 ((not (and curfile require)) nil)
182 ((not (eq require t)) `(:require ,require))
183 (t `(:require
184 ',(intern (file-name-nondirectory
73ceba9f
SM
185 (file-name-sans-extension curfile))))))
186 ,@(nreverse extra-keywords))))
1328a6df 187
b5bbbb76 188 ;; The actual function.
0a74e3bf 189 (defun ,mode (&optional arg ,@extra-args)
b5bbbb76 190 ,(or doc
bff53411
SM
191 (format (concat "Toggle %s on or off.
192Interactively, with no prefix argument, toggle the mode.
5ddfa187 193With universal prefix ARG turn mode on.
b5bbbb76 194With zero or negative ARG turn mode off.
bff53411 195\\{%s}") pretty-name keymap-sym))
5ddfa187
SM
196 ;; Use `toggle' rather than (if ,mode 0 1) so that using
197 ;; repeat-command still does the toggling correctly.
198 (interactive (list (or current-prefix-arg 'toggle)))
b5bbbb76 199 (setq ,mode
5ddfa187
SM
200 (cond
201 ((eq arg 'toggle) (not ,mode))
202 (arg (> (prefix-numeric-value arg) 0))
203 (t
204 (if (null ,mode) t
205 (message
206 "Toggling %s off; better pass an explicit argument."
207 ',mode)
208 nil))))
b5bbbb76
SM
209 ,@body
210 ;; The on/off hooks are here for backward compatibility only.
211 (run-hooks ',hook (if ,mode ',hook-on ',hook-off))
b5bbbb76 212 (if (interactive-p)
d99d3266
SM
213 (progn
214 ,(if globalp `(customize-mark-as-set ',mode))
eb81f275 215 (unless (current-message)
2e2a0075 216 (message ,(format "%s %%sabled" pretty-name)
eb81f275 217 (if ,mode "en" "dis")))))
bff53411 218 (force-mode-line-update)
d99d3266 219 ;; Return the new setting.
b5bbbb76 220 ,mode)
2e2a0075 221
1328a6df
SM
222 ;; Autoloading an easy-mmode-define-minor-mode autoloads
223 ;; everything up-to-here.
224 :autoload-end
225
0a74e3bf
SM
226 ;; The toggle's hook.
227 (defcustom ,hook nil
228 ,(format "Hook run at the end of function `%s'." mode-name)
dce88ea6 229 ,@group
0a74e3bf
SM
230 :type 'hook)
231
d5b037c5 232 ;; Define the minor-mode keymap.
1328a6df 233 ,(unless (symbolp keymap) ;nil is also a symbol.
d5b037c5 234 `(defvar ,keymap-sym
1328a6df
SM
235 (let ((m ,keymap))
236 (cond ((keymapp m) m)
237 ((listp m) (easy-mmode-define-keymap m))
238 (t (error "Invalid keymap %S" ,keymap))))
d5b037c5
SM
239 ,(format "Keymap for `%s'." mode-name)))
240
b5bbbb76 241 (add-minor-mode ',mode ',lighter
1328a6df 242 ,(if keymap keymap-sym
cb5da1a3
SM
243 `(if (boundp ',keymap-sym)
244 (symbol-value ',keymap-sym))))
a1506d29 245
c8c21615 246 ;; If the mode is global, call the function according to the default.
bb76239b
SM
247 ,(if globalp
248 `(if (and load-file-name (not (equal ,init-value ,mode)))
249 (eval-after-load load-file-name '(,mode (if ,mode 1 -1))))))))
5a7a545c 250\f
be22f4cc
SM
251;;;
252;;; make global minor mode
253;;;
254
d5b037c5 255;;;###autoload
be22f4cc
SM
256(defmacro easy-mmode-define-global-mode (global-mode mode turn-on
257 &rest keys)
bff53411 258 "Make GLOBAL-MODE out of the buffer-local minor MODE.
be22f4cc
SM
259TURN-ON is a function that will be called with no args in every buffer
260 and that should try to turn MODE on if applicable for that buffer.
261KEYS is a list of CL-style keyword arguments:
262:group to specify the custom group."
0a74e3bf 263 (let* ((global-mode-name (symbol-name global-mode))
be22f4cc
SM
264 (pretty-name (easy-mmode-pretty-mode-name mode))
265 (pretty-global-name (easy-mmode-pretty-mode-name global-mode))
0a74e3bf
SM
266 (group nil)
267 (extra-args nil)
be22f4cc
SM
268 (buffers (intern (concat global-mode-name "-buffers")))
269 (cmmh (intern (concat global-mode-name "-cmmh"))))
270
271 ;; Check keys.
272 (while (keywordp (car keys))
273 (case (pop keys)
0a74e3bf
SM
274 (:extra-args (setq extra-args (pop keys)))
275 (:group (setq group (nconc group (list :group (pop keys)))))
be22f4cc
SM
276 (t (setq keys (cdr keys)))))
277
0a74e3bf
SM
278 (unless group
279 ;; We might as well provide a best-guess default group.
280 (setq group
dce88ea6
MR
281 `(:group ',(or (custom-current-group)
282 (intern (replace-regexp-in-string
283 "-mode\\'" "" (symbol-name mode)))))))
284
be22f4cc 285 `(progn
be22f4cc
SM
286 ;; The actual global minor-mode
287 (define-minor-mode ,global-mode
288 ,(format "Toggle %s in every buffer.
289With prefix ARG, turn %s on if and only if ARG is positive.
290%s is actually not turned on in every buffer but only in those
291in which `%s' turns it on."
292 pretty-name pretty-global-name pretty-name turn-on)
0a74e3bf 293 :global t :extra-args ,extra-args ,@group
be22f4cc
SM
294
295 ;; Setup hook to handle future mode changes and new buffers.
296 (if ,global-mode
d5b037c5 297 (progn
5ddfa187 298 (add-hook 'find-file-hook ',buffers)
d5b037c5 299 (add-hook 'change-major-mode-hook ',cmmh))
5ddfa187 300 (remove-hook 'find-file-hook ',buffers)
be22f4cc
SM
301 (remove-hook 'change-major-mode-hook ',cmmh))
302
303 ;; Go through existing buffers.
304 (dolist (buf (buffer-list))
305 (with-current-buffer buf
34befa9a 306 (if ,global-mode (,turn-on) (when ,mode (,mode -1))))))
be22f4cc 307
1328a6df
SM
308 ;; Autoloading easy-mmode-define-global-mode
309 ;; autoloads everything up-to-here.
310 :autoload-end
311
be22f4cc
SM
312 ;; List of buffers left to process.
313 (defvar ,buffers nil)
314
315 ;; The function that calls TURN-ON in each buffer.
316 (defun ,buffers ()
be22f4cc 317 (remove-hook 'post-command-hook ',buffers)
d5b037c5
SM
318 (while ,buffers
319 (let ((buf (pop ,buffers)))
320 (when (buffer-live-p buf)
321 (with-current-buffer buf (,turn-on))))))
02565504 322 (put ',buffers 'definition-name ',global-mode)
be22f4cc
SM
323
324 ;; The function that catches kill-all-local-variables.
325 (defun ,cmmh ()
326 (add-to-list ',buffers (current-buffer))
02565504
RS
327 (add-hook 'post-command-hook ',buffers))
328 (put ',cmmh 'definition-name ',global-mode))))
be22f4cc 329
5a7a545c
SM
330;;;
331;;; easy-mmode-defmap
332;;;
333
334(if (fboundp 'set-keymap-parents)
335 (defalias 'easy-mmode-set-keymap-parents 'set-keymap-parents)
336 (defun easy-mmode-set-keymap-parents (m parents)
337 (set-keymap-parent
338 m
339 (cond
340 ((not (consp parents)) parents)
341 ((not (cdr parents)) (car parents))
342 (t (let ((m (copy-keymap (pop parents))))
343 (easy-mmode-set-keymap-parents m parents)
344 m))))))
345
5d78d57d 346;;;###autoload
5a7a545c
SM
347(defun easy-mmode-define-keymap (bs &optional name m args)
348 "Return a keymap built from bindings BS.
349BS must be a list of (KEY . BINDING) where
3837de12
SM
350KEY and BINDINGS are suitable for `define-key'.
351Optional NAME is passed to `make-sparse-keymap'.
352Optional map M can be used to modify an existing map.
165958d2 353ARGS is a list of additional keyword arguments."
eb81f275 354 (let (inherit dense)
5a7a545c
SM
355 (while args
356 (let ((key (pop args))
357 (val (pop args)))
be22f4cc 358 (case key
165958d2 359 (:name (setq name val))
be22f4cc
SM
360 (:dense (setq dense val))
361 (:inherit (setq inherit val))
362 (:group)
5a7a545c
SM
363 (t (message "Unknown argument %s in defmap" key)))))
364 (unless (keymapp m)
365 (setq bs (append m bs))
366 (setq m (if dense (make-keymap name) (make-sparse-keymap name))))
367 (dolist (b bs)
368 (let ((keys (car b))
369 (binding (cdr b)))
370 (dolist (key (if (consp keys) keys (list keys)))
371 (cond
372 ((symbolp key)
373 (substitute-key-definition key binding m global-map))
374 ((null binding)
375 (unless (keymapp (lookup-key m key)) (define-key m key binding)))
376 ((let ((o (lookup-key m key)))
377 (or (null o) (numberp o) (eq o 'undefined)))
378 (define-key m key binding))))))
379 (cond
380 ((keymapp inherit) (set-keymap-parent m inherit))
381 ((consp inherit) (easy-mmode-set-keymap-parents m inherit)))
382 m))
383
384;;;###autoload
385(defmacro easy-mmode-defmap (m bs doc &rest args)
5d78d57d
SM
386 `(defconst ,m
387 (easy-mmode-define-keymap ,bs nil (if (boundp ',m) ,m) ,(cons 'list args))
388 ,doc))
5a7a545c
SM
389
390\f
391;;;
392;;; easy-mmode-defsyntax
393;;;
394
395(defun easy-mmode-define-syntax (css args)
e4fe3460
SM
396 (let ((st (make-syntax-table (plist-get args :copy)))
397 (parent (plist-get args :inherit)))
5a7a545c
SM
398 (dolist (cs css)
399 (let ((char (car cs))
400 (syntax (cdr cs)))
401 (if (sequencep char)
e4ad5f9e 402 (mapcar (lambda (c) (modify-syntax-entry c syntax st)) char)
5a7a545c 403 (modify-syntax-entry char syntax st))))
e4fe3460
SM
404 (if parent (set-char-table-parent
405 st (if (symbolp parent) (symbol-value parent) parent)))
5a7a545c
SM
406 st))
407
408;;;###autoload
409(defmacro easy-mmode-defsyntax (st css doc &rest args)
e4fe3460 410 "Define variable ST as a syntax-table.
2a83a11d 411CSS contains a list of syntax specifications of the form (CHAR . SYNTAX)."
e4ad5f9e
SM
412 `(progn
413 (autoload 'easy-mmode-define-syntax "easy-mmode")
2a83a11d 414 (defconst ,st (easy-mmode-define-syntax ,css ,(cons 'list args)) ,doc)))
5a7a545c
SM
415
416
417\f
c7ea3acc
SM
418;;;
419;;; easy-mmode-define-navigation
420;;;
421
6b61353c 422(defmacro easy-mmode-define-navigation (base re &optional name endfun narrowfun)
c7ea3acc
SM
423 "Define BASE-next and BASE-prev to navigate in the buffer.
424RE determines the places the commands should move point to.
eed083e6 425NAME should describe the entities matched by RE. It is used to build
c7ea3acc
SM
426 the docstrings of the two functions.
427BASE-next also tries to make sure that the whole entry is visible by
428 searching for its end (by calling ENDFUN if provided or by looking for
429 the next entry) and recentering if necessary.
6b61353c
KH
430ENDFUN should return the end position (with or without moving point).
431NARROWFUN non-nil means to check for narrowing before moving, and if
432found, do widen first and then call NARROWFUN with no args after moving."
c7ea3acc
SM
433 (let* ((base-name (symbol-name base))
434 (prev-sym (intern (concat base-name "-prev")))
6b61353c
KH
435 (next-sym (intern (concat base-name "-next")))
436 (check-narrow-maybe
437 (when narrowfun
438 '(setq was-narrowed
439 (prog1 (or (< (- (point-max) (point-min)) (buffer-size)))
440 (widen)))))
441 (re-narrow-maybe (when narrowfun
442 `(when was-narrowed (,narrowfun)))))
8fd9bef2 443 (unless name (setq name base-name))
c7ea3acc 444 `(progn
b5bbbb76
SM
445 (add-to-list 'debug-ignored-errors
446 ,(concat "^No \\(previous\\|next\\) " (regexp-quote name)))
c7ea3acc 447 (defun ,next-sym (&optional count)
36a5b60e 448 ,(format "Go to the next COUNT'th %s." name)
c7ea3acc
SM
449 (interactive)
450 (unless count (setq count 1))
451 (if (< count 0) (,prev-sym (- count))
6b61353c
KH
452 (if (looking-at ,re) (setq count (1+ count)))
453 (let (was-narrowed)
454 ,check-narrow-maybe
455 (if (not (re-search-forward ,re nil t count))
456 (if (looking-at ,re)
457 (goto-char (or ,(if endfun `(,endfun)) (point-max)))
458 (error "No next %s" ,name))
459 (goto-char (match-beginning 0))
460 (when (and (eq (current-buffer) (window-buffer (selected-window)))
461 (interactive-p))
462 (let ((endpt (or (save-excursion
463 ,(if endfun `(,endfun)
464 `(re-search-forward ,re nil t 2)))
465 (point-max))))
466 (unless (pos-visible-in-window-p endpt nil t)
467 (recenter '(0))))))
468 ,re-narrow-maybe)))
c7ea3acc
SM
469 (defun ,prev-sym (&optional count)
470 ,(format "Go to the previous COUNT'th %s" (or name base-name))
471 (interactive)
472 (unless count (setq count 1))
473 (if (< count 0) (,next-sym (- count))
6b61353c
KH
474 (let (was-narrowed)
475 ,check-narrow-maybe
476 (unless (re-search-backward ,re nil t count)
477 (error "No previous %s" ,name))
478 ,re-narrow-maybe))))))
479
5a7a545c 480
6b279740
RS
481(provide 'easy-mmode)
482
6b61353c 483;;; arch-tag: d48a5250-6961-4528-9cb0-3c9ea042a66a
6b279740 484;;; easy-mmode.el ends here