1;;; generic.el --- defining simple major modes with comment and font-lock -*- lexical-binding: t; -*- 2;; 3;; Copyright (C) 1997, 1999, 2001-2021 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;; Package: emacs 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 3 of the License, or 15;; (at your option) 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. If not, see <https://www.gnu.org/licenses/>. 24 25;;; Commentary: 26 27;; INTRODUCTION: 28;; 29;; The macro `define-generic-mode' can be used to define small modes 30;; which provide basic comment and font-lock support. These modes are 31;; intended for the many configuration files and such which are too 32;; small for a "real" mode, but still have a regular syntax, comment 33;; characters and the like. 34;; 35;; Each generic mode can define the following: 36;; 37;; * List of comment-characters. The elements of this list should be 38;; either a character, a one or two character string, or a cons 39;; cell. If the entry is a character or a string, it is added to 40;; the mode's syntax table with "comment starter" syntax. If the 41;; entry is a cons cell, the `car' and `cdr' of the pair are 42;; considered the "comment starter" and "comment ender" 43;; respectively. (The latter should be nil if you want comments to 44;; end at the end of the line.) Emacs does not support comment 45;; strings of more than two characters in length. 46;; 47;; * List of keywords to font-lock in `font-lock-keyword-face'. 48;; Each keyword should be a string. 49;; 50;; * Additional expressions to font-lock. This should be a list of 51;; expressions, each of which should be of the same form as those in 52;; `font-lock-keywords'. 53;; 54;; * List of regular expressions to be placed in auto-mode-alist. 55;; 56;; * List of functions to call to do some additional setup 57;; 58;; This should pretty much cover basic functionality; if you need much 59;; more than this, or you find yourself writing extensive customizations, 60;; perhaps you should be writing a major mode instead! 61;; 62;; EXAMPLE: 63;; 64;; You can use `define-generic-mode' like this: 65;; 66;; (define-generic-mode 'foo-generic-mode 67;; (list ?%) 68;; (list "keyword") 69;; nil 70;; (list "\\.FOO\\'") 71;; (list 'foo-setup-function)) 72;; 73;; to define a new generic-mode `foo-generic-mode', which has '%' as a 74;; comment character, and "keyword" as a keyword. When files which 75;; end in '.FOO' are loaded, Emacs will go into foo-generic-mode and 76;; call foo-setup-function. You can also use the function 77;; `foo-generic-mode' (which is interactive) to put a buffer into 78;; foo-generic-mode. 79;; 80;; GOTCHAS: 81;; 82;; Be careful that your font-lock definitions are correct. Getting 83;; them wrong can cause Emacs to continually attempt to fontify! This 84;; problem is not specific to generic-mode. 85 86;; Credit for suggestions, brainstorming, help with debugging: 87;; ACorreir@pervasive-sw.com (Alfred Correira) 88;; Extensive cleanup by: 89;; Stefan Monnier (monnier+gnu/emacs@flint.cs.yale.edu) 90 91;;; Code: 92 93(eval-when-compile (require 'pcase)) 94 95;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 96;; Internal Variables 97;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 98 99(defvar-local generic-font-lock-keywords nil 100 "Keywords for `font-lock-defaults' in a generic mode.") 101 102;;;###autoload 103(defvar generic-mode-list nil 104 "A list of mode names for `generic-mode'. 105Do not add entries to this list directly; use `define-generic-mode' 106instead (which see).") 107 108;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 109;; Functions 110;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 111 112;;;###autoload 113(defmacro define-generic-mode (mode comment-list keyword-list 114 font-lock-list auto-mode-list 115 function-list &optional docstring) 116 "Create a new generic mode MODE. 117 118A \"generic\" mode is a simple major mode with basic support for 119comment syntax and Font Lock mode, but otherwise does not have 120any special keystrokes or functionality available. 121 122MODE is the name of the command for the generic mode; don't quote it. 123The optional DOCSTRING is the documentation for the mode command. If 124you do not supply it, `define-generic-mode' uses a default 125documentation string instead. 126 127COMMENT-LIST is a list in which each element is either a character, a 128string of one or two characters, or a cons cell. A character or a 129string is set up in the mode's syntax table as a \"comment starter\". 130If the entry is a cons cell, the `car' is set up as a \"comment 131starter\" and the `cdr' as a \"comment ender\". (Use nil for the 132latter if you want comments to end at the end of the line.) Note that 133the syntax table has limitations about what comment starters and 134enders are actually possible. 135 136KEYWORD-LIST is a list of keywords to highlight with 137`font-lock-keyword-face'. Each keyword should be a string. 138 139FONT-LOCK-LIST is a list of additional expressions to highlight. Each 140element of this list should have the same form as an element of 141`font-lock-keywords'. 142 143AUTO-MODE-LIST is a list of regular expressions to add to 144`auto-mode-alist'. These regular expressions are added when Emacs 145runs the macro expansion. 146 147FUNCTION-LIST is a list of functions to call to do some additional 148setup. The mode command calls these functions just before it runs the 149mode hook `MODE-hook'. 150 151See the file generic-x.el for some examples of `define-generic-mode'." 152 (declare (debug (sexp def-form def-form def-form form def-form 153 [&optional stringp] &rest [keywordp form])) 154 (indent 1) 155 (doc-string 7)) 156 157 ;; Backward compatibility. 158 (when (eq (car-safe mode) 'quote) 159 (setq mode (eval mode))) 160 161 (let* ((name (symbol-name mode)) 162 (pretty-name (capitalize (replace-regexp-in-string 163 "-mode\\'" "" name)))) 164 165 `(progn 166 ;; Add a new entry. 167 (add-to-list 'generic-mode-list ,name) 168 169 ;; Add it to auto-mode-alist 170 (dolist (re ,auto-mode-list) 171 (add-to-list 'auto-mode-alist (cons re ',mode))) 172 173 (defun ,mode () 174 ,(or docstring 175 (concat pretty-name " mode.\n" 176 "This a generic mode defined with `define-generic-mode'.\n" 177 "It runs `" name "-hook' as the last thing it does.")) 178 (interactive) 179 (generic-mode-internal ',mode ,comment-list ,keyword-list 180 ,font-lock-list ,function-list))))) 181 182;;;###autoload 183(defun generic-mode-internal (mode comment-list keyword-list 184 font-lock-list function-list) 185 "Go into the generic mode MODE." 186 (let* ((name (symbol-name mode)) 187 (pretty-name (capitalize (replace-regexp-in-string 188 "-mode\\'" "" name))) 189 (mode-hook (intern (concat name "-hook")))) 190 191 (kill-all-local-variables) 192 193 (setq major-mode mode 194 mode-name pretty-name) 195 196 (generic-mode-set-comments comment-list) 197 198 ;; Font-lock functionality. 199 ;; Font-lock-defaults is always set even if there are no keywords 200 ;; or font-lock expressions, so comments can be highlighted. 201 (setq generic-font-lock-keywords font-lock-list) 202 (when keyword-list 203 (push (concat "\\_<" (regexp-opt keyword-list t) "\\_>") 204 generic-font-lock-keywords)) 205 (setq font-lock-defaults '(generic-font-lock-keywords)) 206 207 ;; Call a list of functions 208 (mapc 'funcall function-list) 209 210 (run-mode-hooks mode-hook))) 211 212;;;###autoload 213(defun generic-mode (mode) 214 "Enter generic mode MODE. 215 216Generic modes provide basic comment and font-lock functionality 217for \"generic\" files. (Files which are too small to warrant their 218own mode, but have comment characters, keywords, and the like.) 219 220To define a generic-mode, use the function `define-generic-mode'. 221Some generic modes are defined in `generic-x.el'." 222 (interactive 223 (list (completing-read "Generic mode: " generic-mode-list nil t))) 224 (funcall (intern mode))) 225 226;;; Comment Functionality 227 228(defun generic--normalize-comments (comment-list) 229 (let ((normalized '())) 230 (dolist (start comment-list) 231 (let (end) 232 ;; Normalize 233 (when (consp start) 234 (setq end (cdr start)) 235 (setq start (car start))) 236 (when (characterp start) (setq start (char-to-string start))) 237 (cond 238 ((characterp end) (setq end (char-to-string end))) 239 ((zerop (length end)) (setq end "\n"))) 240 (push (cons start end) normalized))) 241 (nreverse normalized))) 242 243(defun generic-set-comment-syntax (st comment-list) 244 "Set up comment functionality for generic mode." 245 (let ((chars nil) 246 (comstyles) 247 (comment-start nil)) 248 249 ;; Go through all the comments. 250 (pcase-dolist (`(,start . ,end) comment-list) 251 (let ((comstyle 252 ;; Reuse comstyles if necessary. 253 (or (cdr (assoc start comstyles)) 254 (cdr (assoc end comstyles)) 255 ;; Otherwise, use a style not yet in use. 256 (if (not (rassoc "" comstyles)) "") 257 (if (not (rassoc "b" comstyles)) "b") 258 "c"))) 259 (push (cons start comstyle) comstyles) 260 (push (cons end comstyle) comstyles) 261 262 ;; Setup the syntax table. 263 (if (= (length start) 1) 264 (modify-syntax-entry (aref start 0) 265 (concat "< " comstyle) st) 266 (let ((c0 (aref start 0)) (c1 (aref start 1))) 267 ;; Store the relevant info but don't update yet. 268 (push (cons c0 (concat (cdr (assoc c0 chars)) "1")) chars) 269 (push (cons c1 (concat (cdr (assoc c1 chars)) 270 (concat "2" comstyle))) 271 chars))) 272 (if (= (length end) 1) 273 (modify-syntax-entry (aref end 0) 274 (concat ">" comstyle) st) 275 (let ((c0 (aref end 0)) (c1 (aref end 1))) 276 ;; Store the relevant info but don't update yet. 277 (push (cons c0 (concat (cdr (assoc c0 chars)) 278 (concat "3" comstyle))) 279 chars) 280 (push (cons c1 (concat (cdr (assoc c1 chars)) "4")) chars))))) 281 282 ;; Process the chars that were part of a 2-char comment marker 283 (with-syntax-table st ;For `char-syntax'. 284 (dolist (cs (nreverse chars)) 285 (modify-syntax-entry (car cs) 286 (concat (char-to-string (char-syntax (car cs))) 287 " " (cdr cs)) 288 st))))) 289 290(defun generic-set-comment-vars (comment-list) 291 (when comment-list 292 (setq-local comment-start (caar comment-list)) 293 (setq-local comment-end 294 (let ((end (cdar comment-list))) 295 (if (string-equal end "\n") "" end))) 296 (setq-local comment-start-skip 297 (concat (regexp-opt (mapcar #'car comment-list)) 298 "+[ \t]*")) 299 (setq-local comment-end-skip 300 (concat "[ \t]*" (regexp-opt (mapcar #'cdr comment-list)))))) 301 302(defun generic-mode-set-comments (comment-list) 303 "Set up comment functionality for generic mode." 304 (let ((st (make-syntax-table)) 305 (comment-list (generic--normalize-comments comment-list))) 306 (generic-set-comment-syntax st comment-list) 307 (generic-set-comment-vars comment-list) 308 (set-syntax-table st))) 309 310(defun generic-bracket-support () 311 "Imenu support for [KEYWORD] constructs found in INF, INI and Samba files." 312 (setq-local imenu-generic-expression '((nil "^\\[\\(.*\\)\\]" 1))) 313 (setq-local imenu-case-fold-search t)) 314 315;;;###autoload 316(defun generic-make-keywords-list (keyword-list face &optional prefix suffix) 317 "Return a `font-lock-keywords' construct that highlights KEYWORD-LIST. 318KEYWORD-LIST is a list of keyword strings that should be 319highlighted with face FACE. This function calculates a regular 320expression that matches these keywords and concatenates it with 321PREFIX and SUFFIX. Then it returns a construct based on this 322regular expression that can be used as an element of 323`font-lock-keywords'." 324 (declare (obsolete regexp-opt "24.4")) 325 (unless (listp keyword-list) 326 (error "Keywords argument must be a list of strings")) 327 (list (concat prefix "\\_<" 328 ;; Use an optimized regexp. 329 (regexp-opt keyword-list t) 330 "\\_>" suffix) 331 1 332 face)) 333 334(provide 'generic) 335 336;;; generic.el ends here 337