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