]> code.delx.au - gnu-emacs/blob - lisp/derived.el
*** empty log message ***
[gnu-emacs] / lisp / derived.el
1 ;;; derived.el --- allow inheritance of major modes.
2 ;;; (formerly mode-clone.el)
3
4 ;; Copyright (C) 1993, 1994, 1999 Free Software Foundation, Inc.
5
6 ;; Author: David Megginson (dmeggins@aix1.uottawa.ca)
7 ;; Maintainer: FSF
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24 ;; Boston, MA 02111-1307, USA.
25 \f
26 ;;; Commentary:
27
28 ;; Obsolete.
29 ;; Use the `derived-major-mode' provided by easy-mmode.el instead.
30 ;; It is only kept for backward compatibility with byte-compiled files
31 ;; which refer to `derived-mode-init-mode-variables' and other functions.
32
33
34
35 ;; GNU Emacs is already, in a sense, object oriented -- each object
36 ;; (buffer) belongs to a class (major mode), and that class defines
37 ;; the relationship between messages (input events) and methods
38 ;; (commands) by means of a keymap.
39 ;;
40 ;; The only thing missing is a good scheme of inheritance. It is
41 ;; possible to simulate a single level of inheritance with generous
42 ;; use of hooks and a bit of work -- sgml-mode, for example, also runs
43 ;; the hooks for text-mode, and keymaps can inherit from other keymaps
44 ;; -- but generally, each major mode ends up reinventing the wheel.
45 ;; Ideally, someone should redesign all of Emacs's major modes to
46 ;; follow a more conventional object-oriented system: when defining a
47 ;; new major mode, the user should need only to name the existing mode
48 ;; it is most similar to, then list the (few) differences.
49 ;;
50 ;; In the mean time, this package offers most of the advantages of
51 ;; full inheritance with the existing major modes. The macro
52 ;; `define-derived-mode' allows the user to make a variant of an existing
53 ;; major mode, with its own keymap. The new mode will inherit the key
54 ;; bindings of its parent, and will, in fact, run its parent first
55 ;; every time it is called. For example, the commands
56 ;;
57 ;; (define-derived-mode hypertext-mode text-mode "Hypertext"
58 ;; "Major mode for hypertext.\n\n\\{hypertext-mode-map}"
59 ;; (setq case-fold-search nil))
60 ;;
61 ;; (define-key hypertext-mode-map [down-mouse-3] 'do-hyper-link)
62 ;;
63 ;; will create a function `hypertext-mode' with its own (sparse)
64 ;; keymap `hypertext-mode-map.' The command M-x hypertext-mode will
65 ;; perform the following actions:
66 ;;
67 ;; - run the command (text-mode) to get its default setup
68 ;; - replace the current keymap with 'hypertext-mode-map,' which will
69 ;; inherit from 'text-mode-map'.
70 ;; - replace the current syntax table with
71 ;; 'hypertext-mode-syntax-table', which will borrow its defaults
72 ;; from the current text-mode-syntax-table.
73 ;; - replace the current abbrev table with
74 ;; 'hypertext-mode-abbrev-table', which will borrow its defaults
75 ;; from the current text-mode-abbrev table
76 ;; - change the mode line to read "Hypertext"
77 ;; - assign the value 'hypertext-mode' to the 'major-mode' variable
78 ;; - run the body of commands provided in the macro -- in this case,
79 ;; set the local variable `case-fold-search' to nil.
80 ;;
81 ;; The advantages of this system are threefold. First, text mode is
82 ;; untouched -- if you had added the new keystroke to `text-mode-map,'
83 ;; possibly using hooks, you would have added it to all text buffers
84 ;; -- here, it appears only in hypertext buffers, where it makes
85 ;; sense. Second, it is possible to build even further, and make
86 ;; a derived mode from a derived mode. The commands
87 ;;
88 ;; (define-derived-mode html-mode hypertext-mode "HTML")
89 ;; [various key definitions]
90 ;;
91 ;; will add a new major mode for HTML with very little fuss.
92 ;;
93 ;; Note also the function `derived-mode-class,' which returns the non-derived
94 ;; major mode which a derived mode is based on (ie. NOT necessarily the
95 ;; immediate parent).
96 ;;
97 ;; (derived-mode-class 'text-mode) ==> text-mode
98 ;; (derived-mode-class 'hypertext-mode) ==> text-mode
99 ;; (derived-mode-class 'html-mode) ==> text-mode
100 \f
101 ;;; Code:
102
103 ;; PUBLIC: define a new major mode which inherits from an existing one.
104
105 ;; ;;;###autoload
106 (defmacro define-derived-mode (child parent name &optional docstring &rest body)
107 "Create a new mode as a variant of an existing mode.
108
109 The arguments to this command are as follow:
110
111 CHILD: the name of the command for the derived mode.
112 PARENT: the name of the command for the parent mode (e.g. `text-mode').
113 NAME: a string which will appear in the status line (e.g. \"Hypertext\")
114 DOCSTRING: an optional documentation string--if you do not supply one,
115 the function will attempt to invent something useful.
116 BODY: forms to execute just before running the
117 hooks for the new mode.
118
119 Here is how you could define LaTeX-Thesis mode as a variant of LaTeX mode:
120
121 (define-derived-mode LaTeX-thesis-mode LaTeX-mode \"LaTeX-Thesis\")
122
123 You could then make new key bindings for `LaTeX-thesis-mode-map'
124 without changing regular LaTeX mode. In this example, BODY is empty,
125 and DOCSTRING is generated by default.
126
127 On a more complicated level, the following command uses `sgml-mode' as
128 the parent, and then sets the variable `case-fold-search' to nil:
129
130 (define-derived-mode article-mode sgml-mode \"Article\"
131 \"Major mode for editing technical articles.\"
132 (setq case-fold-search nil))
133
134 Note that if the documentation string had been left out, it would have
135 been generated automatically, with a reference to the keymap."
136
137 ; Some trickiness, since what
138 ; appears to be the docstring
139 ; may really be the first
140 ; element of the body.
141 (if (and docstring (not (stringp docstring)))
142 (progn (setq body (cons docstring body))
143 (setq docstring nil)))
144 (setq docstring (or docstring (derived-mode-make-docstring parent child)))
145
146 `(progn
147 (derived-mode-init-mode-variables (quote ,child))
148 (defun ,child ()
149 ,docstring
150 (interactive)
151 ; Run the parent.
152 (,parent)
153 ; Identify special modes.
154 (if (get (quote ,parent) 'special)
155 (put (quote ,child) 'special t))
156 ; Identify the child mode.
157 (setq major-mode (quote ,child))
158 (setq mode-name ,name)
159 ; Set up maps and tables.
160 (derived-mode-set-keymap (quote ,child))
161 (derived-mode-set-syntax-table (quote ,child))
162 (derived-mode-set-abbrev-table (quote ,child))
163 ; Splice in the body (if any).
164 ,@body
165 ;;; ; Run the setup function, if
166 ;;; ; any -- this will soon be
167 ;;; ; obsolete.
168 ;;; (derived-mode-run-setup-function (quote ,child))
169 ; Run the hooks, if any.
170 (derived-mode-run-hooks (quote ,child)))))
171
172
173 ;; PUBLIC: find the ultimate class of a derived mode.
174
175 (defun derived-mode-class (mode)
176 "Find the class of a major MODE.
177 A mode's class is the first ancestor which is NOT a derived mode.
178 Use the `derived-mode-parent' property of the symbol to trace backwards."
179 (while (get mode 'derived-mode-parent)
180 (setq mode (get mode 'derived-mode-parent)))
181 mode)
182
183 \f
184 ;; Inline functions to construct various names from a mode name.
185
186 (defsubst derived-mode-setup-function-name (mode)
187 "Construct a setup-function name based on a MODE name."
188 (intern (concat (symbol-name mode) "-setup")))
189
190 (defsubst derived-mode-hook-name (mode)
191 "Construct the mode hook name based on mode name MODE."
192 (intern (concat (symbol-name mode) "-hook")))
193
194 (defsubst derived-mode-map-name (mode)
195 "Construct a map name based on a MODE name."
196 (intern (concat (symbol-name mode) "-map")))
197
198 (defsubst derived-mode-syntax-table-name (mode)
199 "Construct a syntax-table name based on a MODE name."
200 (intern (concat (symbol-name mode) "-syntax-table")))
201
202 (defsubst derived-mode-abbrev-table-name (mode)
203 "Construct an abbrev-table name based on a MODE name."
204 (intern (concat (symbol-name mode) "-abbrev-table")))
205
206 \f
207 ;; Utility functions for defining a derived mode.
208
209 ;;;###autoload
210 (defun derived-mode-init-mode-variables (mode)
211 "Initialise variables for a new MODE.
212 Right now, if they don't already exist, set up a blank keymap, an
213 empty syntax table, and an empty abbrev table -- these will be merged
214 the first time the mode is used."
215
216 (if (boundp (derived-mode-map-name mode))
217 t
218 (eval `(defvar ,(derived-mode-map-name mode)
219 (make-sparse-keymap)
220 ,(format "Keymap for %s." mode)))
221 (put (derived-mode-map-name mode) 'derived-mode-unmerged t))
222
223 (if (boundp (derived-mode-syntax-table-name mode))
224 t
225 (eval `(defvar ,(derived-mode-syntax-table-name mode)
226 ;; Make a syntax table which doesn't specify anything
227 ;; for any char. Valid data will be merged in by
228 ;; derived-mode-merge-syntax-tables.
229 (make-char-table 'syntax-table nil)
230 ,(format "Syntax table for %s." mode)))
231 (put (derived-mode-syntax-table-name mode) 'derived-mode-unmerged t))
232
233 (if (boundp (derived-mode-abbrev-table-name mode))
234 t
235 (eval `(defvar ,(derived-mode-abbrev-table-name mode)
236 (progn
237 (define-abbrev-table (derived-mode-abbrev-table-name mode) nil)
238 (make-abbrev-table))
239 ,(format "Abbrev table for %s." mode)))))
240
241 (defun derived-mode-make-docstring (parent child)
242 "Construct a docstring for a new mode if none is provided."
243
244 (format "This major mode is a variant of `%s', created by `define-derived-mode'.
245 It inherits all of the parent's attributes, but has its own keymap,
246 abbrev table and syntax table:
247
248 `%s-map' and `%s-syntax-table'
249
250 which more-or-less shadow
251
252 `%s-map' and `%s-syntax-table'
253
254 \\{%s-map}" parent child child parent parent child))
255
256 \f
257 ;; Utility functions for running a derived mode.
258
259 (defun derived-mode-set-keymap (mode)
260 "Set the keymap of the new MODE, maybe merging with the parent."
261 (let* ((map-name (derived-mode-map-name mode))
262 (new-map (eval map-name))
263 (old-map (current-local-map)))
264 (and old-map
265 (get map-name 'derived-mode-unmerged)
266 (derived-mode-merge-keymaps old-map new-map))
267 (put map-name 'derived-mode-unmerged nil)
268 (use-local-map new-map)))
269
270 (defun derived-mode-set-syntax-table (mode)
271 "Set the syntax table of the new MODE, maybe merging with the parent."
272 (let* ((table-name (derived-mode-syntax-table-name mode))
273 (old-table (syntax-table))
274 (new-table (eval table-name)))
275 (if (get table-name 'derived-mode-unmerged)
276 (derived-mode-merge-syntax-tables old-table new-table))
277 (put table-name 'derived-mode-unmerged nil)
278 (set-syntax-table new-table)))
279
280 (defun derived-mode-set-abbrev-table (mode)
281 "Set the abbrev table for MODE if it exists.
282 Always merge its parent into it, since the merge is non-destructive."
283 (let* ((table-name (derived-mode-abbrev-table-name mode))
284 (old-table local-abbrev-table)
285 (new-table (eval table-name)))
286 (derived-mode-merge-abbrev-tables old-table new-table)
287 (setq local-abbrev-table new-table)))
288
289 ;;;(defun derived-mode-run-setup-function (mode)
290 ;;; "Run the setup function if it exists."
291
292 ;;; (let ((fname (derived-mode-setup-function-name mode)))
293 ;;; (if (fboundp fname)
294 ;;; (funcall fname))))
295
296 (defun derived-mode-run-hooks (mode)
297 "Run the mode hook for MODE."
298
299 (let ((hooks-name (derived-mode-hook-name mode)))
300 (if (boundp hooks-name)
301 (run-hooks hooks-name))))
302
303 ;; Functions to merge maps and tables.
304
305 (defun derived-mode-merge-keymaps (old new)
306 "Merge an OLD keymap into a NEW one.
307 The old keymap is set to be the last cdr of the new one, so that there will
308 be automatic inheritance."
309 ;; ?? Can this just use `set-keymap-parent'?
310 (let ((tail new))
311 ;; Scan the NEW map for prefix keys.
312 (while (consp tail)
313 (and (consp (car tail))
314 (let* ((key (vector (car (car tail))))
315 (subnew (lookup-key new key))
316 (subold (lookup-key old key)))
317 ;; If KEY is a prefix key in both OLD and NEW, merge them.
318 (and (keymapp subnew) (keymapp subold)
319 (derived-mode-merge-keymaps subold subnew))))
320 (and (vectorp (car tail))
321 ;; Search a vector of ASCII char bindings for prefix keys.
322 (let ((i (1- (length (car tail)))))
323 (while (>= i 0)
324 (let* ((key (vector i))
325 (subnew (lookup-key new key))
326 (subold (lookup-key old key)))
327 ;; If KEY is a prefix key in both OLD and NEW, merge them.
328 (and (keymapp subnew) (keymapp subold)
329 (derived-mode-merge-keymaps subold subnew)))
330 (setq i (1- i)))))
331 (setq tail (cdr tail))))
332 (setcdr (nthcdr (1- (length new)) new) old))
333
334 (defun derived-mode-merge-syntax-tables (old new)
335 "Merge an OLD syntax table into a NEW one.
336 Where the new table already has an entry, nothing is copied from the old one."
337 (set-char-table-parent new old))
338
339 ;; Merge an old abbrev table into a new one.
340 ;; This function requires internal knowledge of how abbrev tables work,
341 ;; presuming that they are obarrays with the abbrev as the symbol, the expansion
342 ;; as the value of the symbol, and the hook as the function definition.
343 (defun derived-mode-merge-abbrev-tables (old new)
344 (if old
345 (mapatoms
346 (lambda (symbol)
347 (or (intern-soft (symbol-name symbol) new)
348 (define-abbrev new (symbol-name symbol)
349 (symbol-value symbol) (symbol-function symbol))))
350 old)))
351
352 (provide 'derived)
353
354 ;;; derived.el ends here