]> code.delx.au - gnu-emacs/blob - lisp/generic.el
*** empty log message ***
[gnu-emacs] / lisp / generic.el
1 ;;; generic.el --- defining simple major modes with comment and font-lock
2 ;;
3 ;; Copyright (C) 1997, 1999, 2004, 2005 Free Software Foundation, Inc.
4 ;;
5 ;; Author: Peter Breton <pbreton@cs.umb.edu>
6 ;; Created: Fri Sep 27 1996
7 ;; Keywords: generic, comment, font-lock
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
26 ;; Purpose:
27
28 ;; Meta-mode to create simple major modes
29 ;; with basic comment and font-lock support
30
31 ;;; Commentary:
32
33 ;; INTRODUCTION:
34
35 ;; Generic-mode is a meta-mode which can be used to define small modes
36 ;; which provide basic comment and font-lock support. These modes are
37 ;; intended for the many configuration files and such which are too
38 ;; small for a "real" mode, but still have a regular syntax, comment
39 ;; characters and the like.
40 ;;
41 ;; Each generic mode can define the following:
42 ;;
43 ;; * List of comment-characters. The entries in this list should be
44 ;; either a character, a one or two character string or a cons pair.
45 ;; If the entry is a character or a string, it is added to the
46 ;; mode's syntax table with `comment-start' syntax. If the entry is
47 ;; a cons pair, the elements of the pair are considered to be
48 ;; `comment-start' and `comment-end' respectively. (The latter
49 ;; should be nil if you want comments to end at end of line.)
50 ;; LIMITATIONS: Emacs does not support comment strings of more than
51 ;; two characters in length.
52 ;;
53 ;; * List of keywords to font-lock. Each keyword should be a string.
54 ;; If you have additional keywords which should be highlighted in a
55 ;; face different from `font-lock-keyword-face', you can use the
56 ;; convenience function `generic-make-keywords-list' (which see),
57 ;; and add the result to the following list:
58 ;;
59 ;; * Additional expressions to font-lock. This should be a list of
60 ;; expressions, each of which should be of the same form as those in
61 ;; `font-lock-keywords'.
62 ;;
63 ;; * List of regular expressions to be placed in auto-mode-alist.
64 ;;
65 ;; * List of functions to call to do some additional setup
66 ;;
67 ;; This should pretty much cover basic functionality; if you need much
68 ;; more than this, or you find yourself writing extensive customizations,
69 ;; perhaps you should be writing a major mode instead!
70 ;;
71 ;; LOCAL VARIABLES:
72 ;;
73 ;; To put a file into generic mode using local variables, use a line
74 ;; like this in a Local Variables block:
75 ;;
76 ;; mode: default-generic
77 ;;
78 ;; Do NOT use "mode: generic"!
79 ;; See also "AUTOMATICALLY ENTERING GENERIC MODE" below.
80 ;;
81 ;; DEFINING NEW GENERIC MODES:
82 ;;
83 ;; Use the `define-generic-mode' function to define new modes.
84 ;; For example:
85 ;;
86 ;; (define-generic-mode 'foo-generic-mode
87 ;; (list ?%)
88 ;; (list "keyword")
89 ;; nil
90 ;; (list "\\.FOO\\'")
91 ;; (list 'foo-setup-function))
92 ;;
93 ;; defines a new generic-mode `foo-generic-mode', which has '%' as a
94 ;; comment character, and "keyword" as a keyword. When files which
95 ;; end in '.FOO' are loaded, Emacs will go into foo-generic-mode and
96 ;; call foo-setup-function. You can also use the function
97 ;; `foo-generic-mode' (which is interactive) to put a buffer into
98 ;; foo-generic-mode.
99 ;;
100 ;; AUTOMATICALLY ENTERING GENERIC MODE:
101 ;;
102 ;; Generic-mode provides a hook which automatically puts a file into
103 ;; default-generic-mode if the first few lines of a file in
104 ;; fundamental mode start with a hash comment character. To disable
105 ;; this functionality, set the variable `generic-use-find-file-hook'
106 ;; to nil BEFORE loading generic-mode. See the variables
107 ;; `generic-lines-to-scan' and `generic-find-file-regexp' for
108 ;; customization options.
109 ;;
110 ;; GOTCHAS:
111 ;;
112 ;; Be careful that your font-lock definitions are correct. Getting
113 ;; them wrong can cause Emacs to continually attempt to fontify! This
114 ;; problem is not specific to generic-mode.
115 ;;
116
117 ;; Credit for suggestions, brainstorming, help with debugging:
118 ;; ACorreir@pervasive-sw.com (Alfred Correira)
119 ;; Extensive cleanup by:
120 ;; Stefan Monnier (monnier+gnu/emacs@flint.cs.yale.edu)
121 ;;
122 ;;; Code:
123
124 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
125 ;; Internal Variables
126 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
127
128 (defvar generic-font-lock-keywords nil
129 "Keywords for `font-lock-defaults' in a generic mode.")
130 (make-variable-buffer-local 'generic-font-lock-keywords)
131 (defvaralias 'generic-font-lock-defaults 'generic-font-lock-keywords)
132 (make-obsolete-variable 'generic-font-lock-defaults 'generic-font-lock-keywords "22.1")
133
134 ;;;###autoload
135 (defvar generic-mode-list nil
136 "A list of mode names for `generic-mode'.
137 Do not add entries to this list directly; use `define-generic-mode'
138 instead (which see).")
139
140 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
141 ;; Customization Variables
142 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
143
144 (defgroup generic nil
145 "Define simple major modes with comment and font-lock support."
146 :prefix "generic-"
147 :group 'extensions)
148
149 (defcustom generic-use-find-file-hook t
150 "*If non-nil, add a hook to enter `default-generic-mode' automatically.
151 This is done if the first few lines of a file in fundamental mode
152 start with a hash comment character."
153 :group 'generic
154 :type 'boolean)
155
156 (defcustom generic-lines-to-scan 3
157 "*Number of lines that `generic-mode-find-file-hook' looks at.
158 Relevant when deciding whether to enter Default-Generic mode automatically.
159 This variable should be set to a small positive number."
160 :group 'generic
161 :type 'integer)
162
163 (defcustom generic-find-file-regexp "^#"
164 "*Regular expression used by `generic-mode-find-file-hook'.
165 Files in fundamental mode whose first few lines contain a match
166 for this regexp, should be put into Default-Generic mode instead.
167 The number of lines tested for the matches is specified by the
168 value of the variable `generic-lines-to-scan', which see."
169 :group 'generic
170 :type 'regexp)
171
172 (defcustom generic-ignore-files-regexp "[Tt][Aa][Gg][Ss]\\'"
173 "*Regular expression used by `generic-mode-find-file-hook'.
174 Files whose names match this regular expression should not be put
175 into Default-Generic mode, even if they have lines which match
176 the regexp in `generic-find-file-regexp'. If the value is nil,
177 `generic-mode-find-file-hook' does not check the file names."
178 :group 'generic
179 :type '(choice (const :tag "Don't check file names" nil) regexp))
180
181 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
182 ;; Functions
183 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
184
185 ;;;###autoload
186 (defmacro define-generic-mode (mode comment-list keyword-list
187 font-lock-list auto-mode-list
188 function-list &optional docstring)
189 "Create a new generic mode MODE.
190
191 MODE is the name of the command for the generic mode; it need not
192 be quoted. The optional DOCSTRING is the documentation for the
193 mode command. If you do not supply it, a default documentation
194 string will be used instead.
195
196 COMMENT-LIST is a list, whose entries are either a single
197 character, a one or two character string or a cons pair. If the
198 entry is a character or a string, it is added to the mode's
199 syntax table with `comment-start' syntax. If the entry is a cons
200 pair, the elements of the pair are considered to be
201 `comment-start' and `comment-end' respectively. (The latter
202 should be nil if you want comments to end at end of line.) Note
203 that Emacs has limitations regarding comment characters.
204
205 KEYWORD-LIST is a list of keywords to highlight with
206 `font-lock-keyword-face'. Each keyword should be a string.
207
208 FONT-LOCK-LIST is a list of additional expressions to highlight.
209 Each entry in the list should have the same form as an entry in
210 `font-lock-keywords'.
211
212 AUTO-MODE-LIST is a list of regular expressions to add to
213 `auto-mode-alist'. These regexps are added to `auto-mode-alist'
214 as soon as `define-generic-mode' is called.
215
216 FUNCTION-LIST is a list of functions to call to do some
217 additional setup.
218
219 See the file generic-x.el for some examples of `define-generic-mode'."
220 (let* ((name-unquoted (if (eq (car-safe mode) 'quote) ; Backward compatibility.
221 (eval mode)
222 mode))
223 (name-string (symbol-name name-unquoted))
224 (pretty-name (capitalize (replace-regexp-in-string
225 "-mode\\'" "" name-string))))
226
227 `(progn
228 ;; Add a new entry.
229 (add-to-list 'generic-mode-list ,name-string)
230
231 ;; Add it to auto-mode-alist
232 (dolist (re ,auto-mode-list)
233 (add-to-list 'auto-mode-alist (cons re ',name-unquoted)))
234
235 (defun ,name-unquoted ()
236 ,(or docstring
237 (concat pretty-name " mode.\n"
238 "This a generic mode defined with `define-generic-mode'."))
239 (interactive)
240 (generic-mode-internal ',name-unquoted ,comment-list ,keyword-list
241 ,font-lock-list ,function-list)))))
242
243 ;;;###autoload
244 (defun generic-mode-internal (mode comments keywords font-lock-list funs)
245 "Go into the generic mode MODE."
246 (let* ((modename (symbol-name mode))
247 (generic-mode-hooks (intern (concat modename "-hook")))
248 (pretty-name (capitalize (replace-regexp-in-string
249 "-mode\\'" "" modename))))
250
251 (kill-all-local-variables)
252
253 (setq major-mode mode
254 mode-name pretty-name)
255
256 (generic-mode-set-comments comments)
257
258 ;; Font-lock functionality.
259 ;; Font-lock-defaults is always set even if there are no keywords
260 ;; or font-lock expressions, so comments can be highlighted.
261 (setq generic-font-lock-keywords
262 (append
263 (when keywords
264 (list (generic-make-keywords-list keywords font-lock-keyword-face)))
265 font-lock-list))
266 (setq font-lock-defaults '(generic-font-lock-keywords nil))
267
268 ;; Call a list of functions
269 (mapcar 'funcall funs)
270
271 (run-hooks generic-mode-hooks)))
272
273 ;;;###autoload
274 (defun generic-mode (mode)
275 "Enter generic mode MODE.
276
277 Generic modes provide basic comment and font-lock functionality
278 for \"generic\" files. (Files which are too small to warrant their
279 own mode, but have comment characters, keywords, and the like.)
280
281 To define a generic-mode, use the function `define-generic-mode'.
282 Some generic modes are defined in `generic-x.el'."
283 (interactive
284 (list (completing-read "Generic mode: " generic-mode-list nil t)))
285 (funcall (intern mode)))
286
287 ;;; Comment Functionality
288 (defun generic-mode-set-comments (comment-list)
289 "Set up comment functionality for generic mode."
290 (let ((st (make-syntax-table))
291 (chars nil)
292 (comstyles))
293 (make-local-variable 'comment-start)
294 (make-local-variable 'comment-start-skip)
295 (make-local-variable 'comment-end)
296
297 ;; Go through all the comments
298 (dolist (start comment-list)
299 (let (end (comstyle ""))
300 ;; Normalize
301 (when (consp start)
302 (setq end (cdr start))
303 (setq start (car start)))
304 (when (char-valid-p start) (setq start (char-to-string start)))
305 (cond
306 ((char-valid-p end) (setq end (char-to-string end)))
307 ((zerop (length end)) (setq end "\n")))
308
309 ;; Setup the vars for `comment-region'
310 (if comment-start
311 ;; We have already setup a comment-style, so use style b
312 (progn
313 (setq comstyle "b")
314 (setq comment-start-skip
315 (concat comment-start-skip "\\|" (regexp-quote start) "+\\s-*")))
316 ;; First comment-style
317 (setq comment-start start)
318 (setq comment-end (if (string-equal end "\n") "" end))
319 (setq comment-start-skip (concat (regexp-quote start) "+\\s-*")))
320
321 ;; Reuse comstyles if necessary
322 (setq comstyle
323 (or (cdr (assoc start comstyles))
324 (cdr (assoc end comstyles))
325 comstyle))
326 (push (cons start comstyle) comstyles)
327 (push (cons end comstyle) comstyles)
328
329 ;; Setup the syntax table
330 (if (= (length start) 1)
331 (modify-syntax-entry (string-to-char start)
332 (concat "< " comstyle) st)
333 (let ((c0 (elt start 0)) (c1 (elt start 1)))
334 ;; Store the relevant info but don't update yet
335 (push (cons c0 (concat (cdr (assoc c0 chars)) "1")) chars)
336 (push (cons c1 (concat (cdr (assoc c1 chars))
337 (concat "2" comstyle))) chars)))
338 (if (= (length end) 1)
339 (modify-syntax-entry (string-to-char end)
340 (concat ">" comstyle) st)
341 (let ((c0 (elt end 0)) (c1 (elt end 1)))
342 ;; Store the relevant info but don't update yet
343 (push (cons c0 (concat (cdr (assoc c0 chars))
344 (concat "3" comstyle))) chars)
345 (push (cons c1 (concat (cdr (assoc c1 chars)) "4")) chars)))))
346
347 ;; Process the chars that were part of a 2-char comment marker
348 (dolist (cs (nreverse chars))
349 (modify-syntax-entry (car cs)
350 (concat (char-to-string (char-syntax (car cs)))
351 " " (cdr cs))
352 st))
353 (set-syntax-table st)))
354
355 (defun generic-bracket-support ()
356 "Imenu support for [KEYWORD] constructs found in INF, INI and Samba files."
357 (setq imenu-generic-expression
358 '((nil "^\\[\\(.*\\)\\]" 1))
359 imenu-case-fold-search t))
360
361 ;; This generic mode is always defined
362 (define-generic-mode default-generic-mode (list ?#) nil nil nil nil)
363
364 ;; A more general solution would allow us to enter generic-mode for
365 ;; *any* comment character, but would require us to synthesize a new
366 ;; generic-mode on the fly. I think this gives us most of what we
367 ;; want.
368 (defun generic-mode-find-file-hook ()
369 "Hook function to enter Default-Generic mode automatically.
370
371 Done if the first few lines of a file in Fundamental mode start
372 with a match for the regexp in `generic-find-file-regexp', unless
373 the file's name matches the regexp which is the value of the
374 variable `generic-ignore-files-regexp'.
375
376 This hook will be installed if the variable
377 `generic-use-find-file-hook' is non-nil. The variable
378 `generic-lines-to-scan' determines the number of lines to look at."
379 (when (and (eq major-mode 'fundamental-mode)
380 (or (null generic-ignore-files-regexp)
381 (not (string-match
382 generic-ignore-files-regexp
383 (file-name-sans-versions buffer-file-name)))))
384 (save-excursion
385 (goto-char (point-min))
386 (when (re-search-forward generic-find-file-regexp
387 (save-excursion
388 (forward-line generic-lines-to-scan)
389 (point)) t)
390 (goto-char (point-min))
391 (default-generic-mode)))))
392
393 (defun generic-mode-ini-file-find-file-hook ()
394 "Hook function to enter Default-Generic mode automatically for INI files.
395 Done if the first few lines of a file in Fundamental mode look like an
396 INI file. This hook is NOT installed by default."
397 (and (eq major-mode 'fundamental-mode)
398 (save-excursion
399 (goto-char (point-min))
400 (and (looking-at "^\\s-*\\[.*\\]")
401 (ini-generic-mode)))))
402
403 (and generic-use-find-file-hook
404 (add-hook 'find-file-hook 'generic-mode-find-file-hook))
405
406 ;;;###autoload
407 (defun generic-make-keywords-list (keywords-list face &optional prefix suffix)
408 "Return a regular expression matching the specified KEYWORDS-LIST.
409 The regexp is highlighted with FACE."
410 (unless (listp keywords-list)
411 (error "Keywords argument must be a list of strings"))
412 (list (concat prefix "\\_<"
413 ;; Use an optimized regexp.
414 (regexp-opt keywords-list t)
415 "\\_>" suffix)
416 1
417 face))
418
419 (provide 'generic)
420
421 ;; arch-tag: 239c1fc4-1303-48d9-9ac0-657d655669ea
422 ;;; generic.el ends here