summaryrefslogtreecommitdiff
path: root/lisp/emacs-lisp/generic.el
blob: 93f780eac2f01f88baa385c53fa4a26c2bd0892b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
;;; generic.el --- defining simple major modes with comment and font-lock
;;
;; Copyright (C) 1997, 1999, 2001-2021 Free Software Foundation, Inc.
;;
;; Author: Peter Breton <pbreton@cs.umb.edu>
;; Created: Fri Sep 27 1996
;; Keywords: generic, comment, font-lock
;; Package: emacs

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; INTRODUCTION:
;;
;; The macro `define-generic-mode' can be used to define small modes
;; which provide basic comment and font-lock support.  These modes are
;; intended for the many configuration files and such which are too
;; small for a "real" mode, but still have a regular syntax, comment
;; characters and the like.
;;
;; Each generic mode can define the following:
;;
;; * List of comment-characters.  The elements of this list should be
;;   either a character, a one or two character string, or a cons
;;   cell.  If the entry is a character or a string, it is added to
;;   the mode's syntax table with "comment starter" syntax.  If the
;;   entry is a cons cell, the `car' and `cdr' of the pair are
;;   considered the "comment starter" and "comment ender"
;;   respectively.  (The latter should be nil if you want comments to
;;   end at the end of the line.)  Emacs does not support comment
;;   strings of more than two characters in length.
;;
;; * List of keywords to font-lock in `font-lock-keyword-face'.
;;   Each keyword should be a string.
;;
;; * Additional expressions to font-lock.  This should be a list of
;;   expressions, each of which should be of the same form as those in
;;   `font-lock-keywords'.
;;
;; * List of regular expressions to be placed in auto-mode-alist.
;;
;; * List of functions to call to do some additional setup
;;
;; This should pretty much cover basic functionality; if you need much
;; more than this, or you find yourself writing extensive customizations,
;; perhaps you should be writing a major mode instead!
;;
;; EXAMPLE:
;;
;; You can use `define-generic-mode' like this:
;;
;;   (define-generic-mode 'foo-generic-mode
;;     (list ?%)
;;     (list "keyword")
;;     nil
;;     (list "\\.FOO\\'")
;;     (list 'foo-setup-function))
;;
;; to define a new generic-mode `foo-generic-mode', which has '%' as a
;; comment character, and "keyword" as a keyword.  When files which
;; end in '.FOO' are loaded, Emacs will go into foo-generic-mode and
;; call foo-setup-function.  You can also use the function
;; `foo-generic-mode' (which is interactive) to put a buffer into
;; foo-generic-mode.
;;
;; GOTCHAS:
;;
;; Be careful that your font-lock definitions are correct.  Getting
;; them wrong can cause Emacs to continually attempt to fontify! This
;; problem is not specific to generic-mode.

;; Credit for suggestions, brainstorming, help with debugging:
;;   ACorreir@pervasive-sw.com (Alfred Correira)
;; Extensive cleanup by:
;;   Stefan Monnier (monnier+gnu/emacs@flint.cs.yale.edu)

;;; Code:

(eval-when-compile (require 'pcase))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Internal Variables
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(defvar generic-font-lock-keywords nil
  "Keywords for `font-lock-defaults' in a generic mode.")
(make-variable-buffer-local 'generic-font-lock-keywords)

;;;###autoload
(defvar generic-mode-list nil
  "A list of mode names for `generic-mode'.
Do not add entries to this list directly; use `define-generic-mode'
instead (which see).")

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;###autoload
(defmacro define-generic-mode (mode comment-list keyword-list
				    font-lock-list auto-mode-list
				    function-list &optional docstring)
  "Create a new generic mode MODE.

A \"generic\" mode is a simple major mode with basic support for
comment syntax and Font Lock mode, but otherwise does not have
any special keystrokes or functionality available.

MODE is the name of the command for the generic mode; don't quote it.
The optional DOCSTRING is the documentation for the mode command.  If
you do not supply it, `define-generic-mode' uses a default
documentation string instead.

COMMENT-LIST is a list in which each element is either a character, a
string of one or two characters, or a cons cell.  A character or a
string is set up in the mode's syntax table as a \"comment starter\".
If the entry is a cons cell, the `car' is set up as a \"comment
starter\" and the `cdr' as a \"comment ender\".  (Use nil for the
latter if you want comments to end at the end of the line.)  Note that
the syntax table has limitations about what comment starters and
enders are actually possible.

KEYWORD-LIST is a list of keywords to highlight with
`font-lock-keyword-face'.  Each keyword should be a string.

FONT-LOCK-LIST is a list of additional expressions to highlight.  Each
element of this list should have the same form as an element of
`font-lock-keywords'.

AUTO-MODE-LIST is a list of regular expressions to add to
`auto-mode-alist'.  These regular expressions are added when Emacs
runs the macro expansion.

FUNCTION-LIST is a list of functions to call to do some additional
setup.  The mode command calls these functions just before it runs the
mode hook `MODE-hook'.

See the file generic-x.el for some examples of `define-generic-mode'."
  (declare (debug (sexp def-form def-form def-form form def-form
			[&optional stringp] &rest [keywordp form]))
	   (indent 1)
           (doc-string 7))

  ;; Backward compatibility.
  (when (eq (car-safe mode) 'quote)
    (setq mode (eval mode)))

  (let* ((name (symbol-name mode))
	 (pretty-name (capitalize (replace-regexp-in-string
				   "-mode\\'" "" name))))

    `(progn
       ;; Add a new entry.
       (add-to-list 'generic-mode-list ,name)

       ;; Add it to auto-mode-alist
       (dolist (re ,auto-mode-list)
	 (add-to-list 'auto-mode-alist (cons re ',mode)))

       (defun ,mode ()
	 ,(or docstring
	      (concat pretty-name " mode.\n"
		      "This a generic mode defined with `define-generic-mode'.\n"
		      "It runs `" name "-hook' as the last thing it does."))
	 (interactive)
	 (generic-mode-internal ',mode ,comment-list ,keyword-list
				,font-lock-list ,function-list)))))

;;;###autoload
(defun generic-mode-internal (mode comment-list keyword-list
				   font-lock-list function-list)
  "Go into the generic mode MODE."
  (let* ((name (symbol-name mode))
	 (pretty-name (capitalize (replace-regexp-in-string
				   "-mode\\'" "" name)))
	 (mode-hook (intern (concat name "-hook"))))

    (kill-all-local-variables)

    (setq major-mode mode
	  mode-name pretty-name)

    (generic-mode-set-comments comment-list)

    ;; Font-lock functionality.
    ;; Font-lock-defaults is always set even if there are no keywords
    ;; or font-lock expressions, so comments can be highlighted.
    (setq generic-font-lock-keywords font-lock-list)
    (when keyword-list
      (push (concat "\\_<" (regexp-opt keyword-list t) "\\_>")
	    generic-font-lock-keywords))
    (setq font-lock-defaults '(generic-font-lock-keywords))

    ;; Call a list of functions
    (mapc 'funcall function-list)

    (run-mode-hooks mode-hook)))

;;;###autoload
(defun generic-mode (mode)
  "Enter generic mode MODE.

Generic modes provide basic comment and font-lock functionality
for \"generic\" files.  (Files which are too small to warrant their
own mode, but have comment characters, keywords, and the like.)

To define a generic-mode, use the function `define-generic-mode'.
Some generic modes are defined in `generic-x.el'."
  (interactive
   (list (completing-read "Generic mode: " generic-mode-list nil t)))
  (funcall (intern mode)))

;;; Comment Functionality

(defun generic--normalize-comments (comment-list)
  (let ((normalized '()))
    (dolist (start comment-list)
      (let (end)
	;; Normalize
	(when (consp start)
	  (setq end (cdr start))
	  (setq start (car start)))
	(when (characterp start) (setq start (char-to-string start)))
	(cond
	 ((characterp end)   (setq end (char-to-string end)))
	 ((zerop (length end)) (setq end "\n")))
        (push (cons start end) normalized)))
    (nreverse normalized)))

(defun generic-set-comment-syntax (st comment-list)
  "Set up comment functionality for generic mode."
  (let ((chars nil)
	(comstyles)
        (comstyle "")
        (comment-start nil))

    ;; Go through all the comments.
    (pcase-dolist (`(,start . ,end) comment-list)
      (let ((comstyle
             ;; Reuse comstyles if necessary.
             (or (cdr (assoc start comstyles))
                 (cdr (assoc end comstyles))
                 ;; Otherwise, use a style not yet in use.
                 (if (not (rassoc "" comstyles)) "")
                 (if (not (rassoc "b" comstyles)) "b")
                 "c")))
       (push (cons start comstyle) comstyles)
       (push (cons end comstyle) comstyles)

	;; Setup the syntax table.
	(if (= (length start) 1)
	    (modify-syntax-entry (aref start 0)
				 (concat "< " comstyle) st)
	  (let ((c0 (aref start 0)) (c1 (aref start 1)))
	    ;; Store the relevant info but don't update yet.
	    (push (cons c0 (concat (cdr (assoc c0 chars)) "1")) chars)
	    (push (cons c1 (concat (cdr (assoc c1 chars))
				   (concat "2" comstyle))) chars)))
	(if (= (length end) 1)
	    (modify-syntax-entry (aref end 0)
				 (concat ">" comstyle) st)
	  (let ((c0 (aref end 0)) (c1 (aref end 1)))
	    ;; Store the relevant info but don't update yet.
	    (push (cons c0 (concat (cdr (assoc c0 chars))
				   (concat "3" comstyle))) chars)
	    (push (cons c1 (concat (cdr (assoc c1 chars)) "4")) chars)))))

    ;; Process the chars that were part of a 2-char comment marker
    (with-syntax-table st               ;For `char-syntax'.
    (dolist (cs (nreverse chars))
      (modify-syntax-entry (car cs)
			   (concat (char-to-string (char-syntax (car cs)))
				   " " (cdr cs))
                             st)))))

(defun generic-set-comment-vars (comment-list)
  (when comment-list
    (setq-local comment-start (caar comment-list))
    (setq-local comment-end
                (let ((end (cdar comment-list)))
                  (if (string-equal end "\n") "" end)))
    (setq-local comment-start-skip
                (concat (regexp-opt (mapcar #'car comment-list))
                        "+[ \t]*"))
    (setq-local comment-end-skip
                (concat "[ \t]*" (regexp-opt (mapcar #'cdr comment-list))))))

(defun generic-mode-set-comments (comment-list)
  "Set up comment functionality for generic mode."
  (let ((st (make-syntax-table))
        (comment-list (generic--normalize-comments comment-list)))
    (generic-set-comment-syntax st comment-list)
    (generic-set-comment-vars comment-list)
    (set-syntax-table st)))

(defun generic-bracket-support ()
  "Imenu support for [KEYWORD] constructs found in INF, INI and Samba files."
  (setq-local imenu-generic-expression '((nil "^\\[\\(.*\\)\\]" 1)))
  (setq-local imenu-case-fold-search t))

;;;###autoload
(defun generic-make-keywords-list (keyword-list face &optional prefix suffix)
  "Return a `font-lock-keywords' construct that highlights KEYWORD-LIST.
KEYWORD-LIST is a list of keyword strings that should be
highlighted with face FACE.  This function calculates a regular
expression that matches these keywords and concatenates it with
PREFIX and SUFFIX.  Then it returns a construct based on this
regular expression that can be used as an element of
`font-lock-keywords'."
  (declare (obsolete regexp-opt "24.4"))
  (unless (listp keyword-list)
    (error "Keywords argument must be a list of strings"))
  (list (concat prefix "\\_<"
		;; Use an optimized regexp.
		(regexp-opt keyword-list t)
		"\\_>" suffix)
	1
	face))

(provide 'generic)

;;; generic.el ends here