]> code.delx.au - gnu-emacs/blobdiff - lisp/textmodes/conf-mode.el
Add 2010 to copyright years.
[gnu-emacs] / lisp / textmodes / conf-mode.el
index cb6926169471598edc38a81f03333621e652304c..667818db3498145c5d80cdeef9db34c4c93caaa7 100644 (file)
@@ -1,14 +1,17 @@
 ;;; conf-mode.el --- Simple major mode for editing conf/ini/properties files
 
-;; Copyright (C) 2004 by Daniel Pfeiffer <occitan@esperanto.org>
+;; Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010
+;;   Free Software Foundation, Inc.
+
+;; Author: Daniel Pfeiffer <occitan@esperanto.org>
 ;; Keywords: conf ini windows java
 
 ;; This file is part of GNU Emacs.
 
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; 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 2, or (at your option)
-;; any later version.
+;; 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
@@ -16,9 +19,7 @@
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
 ;;; Commentary:
 ;;
 
 (require 'newcomment)
 
+(defvar outline-heading-end-regexp)
+
 ;; Variables:
 
 (defgroup conf nil
   "Configuration files."
   :group 'data
-  :version "21.4")
+  :version "22.1")
 
 (defcustom conf-assignment-column 24
   "Align assignments to this column by default with \\[conf-align-assignments].
@@ -65,23 +68,67 @@ not align (only setting space according to `conf-assignment-space')."
   :type 'boolean
   :group 'conf)
 
-
 (defvar conf-mode-map
-  (let ((map (make-sparse-keymap)))
+  (let ((map (make-sparse-keymap))
+       (menu-map (make-sparse-keymap)))
     (define-key map "\C-c\C-u" 'conf-unix-mode)
     (define-key map "\C-c\C-w" 'conf-windows-mode)
     (define-key map "\C-c\C-j" 'conf-javaprop-mode)
-    (define-key map "\C-c\C-s" 'conf-space-mode)
-    (define-key map "\C-c " 'conf-space-mode)
+    (define-key map "\C-c\C-s" 'conf-space-keywords)
+    (define-key map "\C-c " 'conf-space-keywords)
     (define-key map "\C-c\C-c" 'conf-colon-mode)
     (define-key map "\C-c:" 'conf-colon-mode)
     (define-key map "\C-c\C-x" 'conf-xdefaults-mode)
+    (define-key map "\C-c\C-p" 'conf-ppd-mode)
     (define-key map "\C-c\C-q" 'conf-quote-normal)
     (define-key map "\C-c\"" 'conf-quote-normal)
     (define-key map "\C-c'" 'conf-quote-normal)
     (define-key map "\C-c\C-a" 'conf-align-assignments)
+    (define-key map [menu-bar sh-script] (cons "Conf" menu-map))
+    (define-key menu-map [conf-windows-mode]
+      '(menu-item "Windows mode"
+                 conf-windows-mode
+                 :help "Conf Mode starter for Windows style Conf files"
+                 :button (:radio . (eq major-mode 'conf-windows-mode))))
+    (define-key menu-map [conf-javaprop-mode]
+      '(menu-item "Java properties mode"
+                 conf-javaprop-mode
+                 :help "Conf Mode starter for Java properties files"
+                 :button (:radio . (eq major-mode 'conf-javaprop-mode))))
+    (define-key menu-map [conf-space-keywords]
+      '(menu-item "Space keywords mode..."
+                 conf-space-keywords
+                 :help "Enter Conf Space mode using regexp KEYWORDS to match the keywords"
+                 :button (:radio . (eq major-mode 'conf-space-keywords))))
+    (define-key menu-map [conf-ppd-mode]
+      '(menu-item "PPD mode"
+                 conf-ppd-mode
+                 :help "Conf Mode starter for Adobe/CUPS PPD files"
+                 :button (:radio . (eq major-mode 'conf-ppd-mode))))
+    (define-key menu-map [conf-colon-mode]
+      '(menu-item "Colon mode"
+                 conf-colon-mode
+                 :help "Conf Mode starter for Colon files"
+                 :button (:radio . (eq major-mode 'conf-colon-mode))))
+    (define-key menu-map [conf-unix-mode]
+      '(menu-item "Unix mode"
+                 conf-unix-mode
+                 :help "Conf Mode starter for Unix style Conf files"
+                 :button (:radio . (eq major-mode 'conf-unix-mode))))
+    (define-key menu-map [conf-xdefaults-mode]
+      '(menu-item "Xdefaults mode"
+                 conf-xdefaults-mode
+                 :help "Conf Mode starter for Xdefaults files"
+                 :button (:radio . (eq major-mode 'conf-xdefaults-mode))))
+    (define-key menu-map [c-s0] '("--"))
+    (define-key menu-map [conf-quote-normal]
+      '(menu-item "Set quote syntax normal" conf-quote-normal
+                 :help "Set the syntax of \' and \" to punctuation"))
+    (define-key menu-map [conf-align-assignments]
+      '(menu-item "Align assignments" conf-align-assignments
+                 :help "Align assignments"))
     map)
-  "Local keymap for conf-mode buffers.")
+  "Local keymap for `conf-mode' buffers.")
 
 (defvar conf-mode-syntax-table
   (let ((table (make-syntax-table)))
@@ -90,12 +137,11 @@ not align (only setting space according to `conf-assignment-space')."
     (modify-syntax-entry ?-  "_" table)
     (modify-syntax-entry ?.  "_" table)
     (modify-syntax-entry ?\' "\"" table)
-;    (modify-syntax-entry ?:  "_" table)
     (modify-syntax-entry ?\; "<" table)
     (modify-syntax-entry ?\n ">" table)
     (modify-syntax-entry ?\r ">" table)
     table)
-  "Syntax table in use in Windows style conf-mode buffers.")
+  "Syntax table in use in Windows style `conf-mode' buffers.")
 
 (defvar conf-unix-mode-syntax-table
   (let ((table (make-syntax-table conf-mode-syntax-table)))
@@ -103,7 +149,7 @@ not align (only setting space according to `conf-assignment-space')."
     ;; override
     (modify-syntax-entry ?\; "." table)
     table)
-  "Syntax table in use in Unix style conf-mode buffers.")
+  "Syntax table in use in Unix style `conf-mode' buffers.")
 
 (defvar conf-javaprop-mode-syntax-table
   (let ((table (make-syntax-table conf-unix-mode-syntax-table)))
@@ -112,13 +158,23 @@ not align (only setting space according to `conf-assignment-space')."
     table)
   "Syntax table in use in Java prperties buffers.")
 
+(defvar conf-ppd-mode-syntax-table
+  (let ((table (make-syntax-table conf-mode-syntax-table)))
+    (modify-syntax-entry ?*  ". 1" table)
+    (modify-syntax-entry ?%  ". 2" table)
+    ;; override
+    (modify-syntax-entry ?\' "." table)
+    (modify-syntax-entry ?\; "." table)
+    table)
+  "Syntax table in use in PPD `conf-mode' buffers.")
+
 (defvar conf-xdefaults-mode-syntax-table
   (let ((table (make-syntax-table conf-mode-syntax-table)))
     (modify-syntax-entry ?!  "<" table)
     ;; override
     (modify-syntax-entry ?\; "." table)
     table)
-  "Syntax table in use in Xdefaults style conf-mode buffers.")
+  "Syntax table in use in Xdefaults style `conf-mode' buffers.")
 
 
 (defvar conf-font-lock-keywords
@@ -130,7 +186,7 @@ not align (only setting space according to `conf-assignment-space')."
      (2 'font-lock-constant-face nil t))
     ;; section { ... } (do this last because some assign ...{...)
     ("^[ \t]*\\([^=:\n]+?\\)[ \t\n]*{[^{}]*?$" 1 'font-lock-type-face prepend))
-  "Keywords to hilight in Conf mode")
+  "Keywords to hilight in Conf mode.")
 
 (defvar conf-javaprop-font-lock-keywords
   '(;; var=val
@@ -142,7 +198,7 @@ not align (only setting space according to `conf-assignment-space')."
      (5 'font-lock-variable-name-face nil t)
      (6 'font-lock-constant-face nil t)
      (7 'font-lock-variable-name-face nil t)))
-  "Keywords to hilight in Conf Java Properties mode")
+  "Keywords to hilight in Conf Java Properties mode.")
 
 (defvar conf-space-keywords-alist
   '(("\\`/etc/gpm/" . "key\\|name\\|foreground\\|background\\|border\\|head")
@@ -154,7 +210,7 @@ not align (only setting space according to `conf-assignment-space')."
     ("/resmgr\\.conf" . "class\\|add\\|allow\\|deny")
     ("/dictionary\\.lst\\'" . "DICT\\|HYPH\\|THES")
     ("/tuxracer/options" . "set"))
-  "File name based settings for `conf-space-keywords'.")
+  "File-name-based settings for the variable `conf-space-keywords'.")
 
 (defvar conf-space-keywords nil
   "Regexps for functions that may come before a space assignment.
@@ -162,6 +218,7 @@ This allows constructs such as
 keyword var value
 This variable is best set in the file local variables, or through
 `conf-space-keywords-alist'.")
+(put 'conf-space-keywords 'safe-local-variable 'stringp)
 
 (defvar conf-space-font-lock-keywords
   `(;; [section] (do this first because it may look like a parameter)
@@ -174,7 +231,7 @@ This variable is best set in the file local variables, or through
                '(1 'font-lock-keyword-face)
                '(2 'font-lock-variable-name-face))
          '("^[ \t]*\\([^\000- ]+\\)" 1 'font-lock-variable-name-face)))
-  "Keywords to hilight in Conf Space mode")
+  "Keywords to highlight in Conf Space mode.")
 
 (defvar conf-colon-font-lock-keywords
   `(;; [section] (do this first because it may look like a parameter)
@@ -184,14 +241,14 @@ This variable is best set in the file local variables, or through
      (1 'font-lock-variable-name-face))
     ;; section { ... } (do this last because some assign ...{...)
     ("^[ \t]*\\([^:\n]+\\)[ \t\n]*{[^{}]*?$" 1 'font-lock-type-face prepend))
-  "Keywords to hilight in Conf Colon mode")
+  "Keywords to hilight in Conf Colon mode.")
 
 (defvar conf-assignment-sign ?=
-  "What sign is used for assignments.")
+  "Sign used for assignments (char or string).")
 
 (defvar conf-assignment-regexp ".+?\\([ \t]*=[ \t]*\\)"
   "Regexp to recognize assignments.
-It is anchored after the first sexp on a line.  There must a
+It is anchored after the first sexp on a line.  There must be a
 grouping for the assignment sign, including leading and trailing
 whitespace.")
 
@@ -200,52 +257,63 @@ whitespace.")
 ;; `align', I'd be glad to hear.
 (defun conf-align-assignments (&optional arg)
   (interactive "P")
+  "Align the assignments in the buffer or active region.
+In Transient Mark mode, if the mark is active, operate on the
+contents of the region.  Otherwise, operate on the whole buffer."
   (setq arg (if arg
                (prefix-numeric-value arg)
              conf-assignment-column))
   (save-excursion
-    (goto-char (point-min))
-    (while (not (eobp))
-      (let ((cs (comment-beginning)))  ; go before comment if within
-       (if cs (goto-char cs)))
-      (while (forward-comment 9))      ; max-int?
-      (when (and (not (eobp))
-                (looking-at conf-assignment-regexp))
-       (goto-char (match-beginning 1))
-       (delete-region (point) (match-end 1))
-       (if conf-assignment-sign
-           (if (>= arg 0)
-               (progn
-                 (indent-to-column arg)
-                 (or (not conf-assignment-space) (memq (char-before (point)) '(?  ?\t)) (insert ? ))
-                 (insert conf-assignment-sign (if (and conf-assignment-space (not (eolp))) ?\  "")))
-             (insert (if conf-assignment-space ?\  "") conf-assignment-sign)
-             (unless (eolp)
-               (indent-to-column (- arg))
-               (or (not conf-assignment-space) (memq (char-before (point)) '(?  ?\t)) (insert ? ))))
-         (unless (eolp)
-           (if (>= (current-column) (abs arg))
-               (insert ? )
-             (indent-to-column (abs arg))))))
-      (forward-line))))
-
-
-(defun conf-quote-normal ()
-  "Set the syntax of \" and ' to punctuation.
+    (save-restriction
+      (when (use-region-p)
+       (narrow-to-region (region-beginning) (region-end)))
+      (goto-char (point-min))
+      (while (not (eobp))
+       (let ((cs (comment-beginning))) ; go before comment if within
+         (if cs (goto-char cs)))
+       (while (forward-comment 9))     ; max-int?
+       (when (and (not (eobp))
+                  (looking-at conf-assignment-regexp))
+         (goto-char (match-beginning 1))
+         (delete-region (point) (match-end 1))
+         (if conf-assignment-sign
+             (if (>= arg 0)
+                 (progn
+                   (indent-to-column arg)
+                   (or (not conf-assignment-space)
+                       (memq (char-before (point)) '(?\s ?\t)) (insert ?\s))
+                   (insert conf-assignment-sign
+                           (if (and conf-assignment-space (not (eolp))) ?\s "")))
+               (insert (if conf-assignment-space ?\s "") conf-assignment-sign)
+               (unless (eolp)
+                 (indent-to-column (- arg))
+                 (or (not conf-assignment-space)
+                     (memq (char-before (point)) '(?\s ?\t)) (insert ?\s))))
+           (unless (eolp)
+             (if (>= (current-column) (abs arg))
+                 (insert ?\s)
+               (indent-to-column (abs arg))))))
+       (forward-line)))))
+
+
+(defun conf-quote-normal (arg)
+  "Set the syntax of ' and \" to punctuation.
+With prefix arg, only do it for ' if 1, or only for \" if 2.
 This only affects the current buffer.  Some conf files use quotes
 to delimit strings, while others allow quotes as simple parts of
 the assigned value.  In those files font locking will be wrong,
 and you can correct it with this command.  (Some files even do
 both, i.e. quotes delimit strings, except when they are
 unbalanced, but hey...)"
-  (interactive)
+  (interactive "P")
   (let ((table (copy-syntax-table (syntax-table))))
-    (modify-syntax-entry ?\" "." table)
-    (modify-syntax-entry ?\' "." table)
+    (when (or (not arg) (= (prefix-numeric-value arg) 1))
+      (modify-syntax-entry ?\' "." table))
+    (when (or (not arg) (= (prefix-numeric-value arg) 2))
+      (modify-syntax-entry ?\" "." table))
     (set-syntax-table table)
-    (and (boundp 'font-lock-mode)
-        font-lock-mode
-        (font-lock-fontify-buffer))))
+    (when font-lock-mode
+      (font-lock-fontify-buffer))))
 
 
 (defun conf-outline-level ()
@@ -259,7 +327,7 @@ unbalanced, but hey...)"
 \f
 
 ;;;###autoload
-(defun conf-mode (&optional comment syntax-table name)
+(defun conf-mode ()
   "Mode for Unix and Windows Conf files and Java properties.
 Most conf files know only three kinds of constructs: parameter
 assignments optionally grouped into sections and comments.  Yet
@@ -284,14 +352,20 @@ If instead you start this mode with the generic `conf-mode'
 command, it will parse the buffer.  It will generally well
 identify the first four cases listed below.  If the buffer
 doesn't have enough contents to decide, this is identical to
-`conf-windows-mode' on Windows, elsewhere to `conf-unix-mode'.  See
-also `conf-space-mode', `conf-colon-mode', `conf-javaprop-mode' and
-`conf-xdefaults-mode'.
+`conf-windows-mode' on Windows, elsewhere to `conf-unix-mode'.
+See also `conf-space-mode', `conf-colon-mode', `conf-javaprop-mode',
+`conf-ppd-mode' and `conf-xdefaults-mode'.
 
 \\{conf-mode-map}"
 
   (interactive)
-  (if (not comment)
+  ;; `conf-mode' plays two roles: it's the parent of several sub-modes
+  ;; but it's also the function that chooses between those submodes.
+  ;; To tell the difference between those two cases where the function
+  ;; might be called, we check `delay-mode-hooks'.
+  ;; (adopted from tex-mode.el)
+  (if (not delay-mode-hooks)
+      ;; try to guess sub-mode of conf-mode based on buffer content
       (let ((unix 0) (win 0) (equal 0) (colon 0) (space 0) (jp 0))
        (save-excursion
          (goto-char (point-min))
@@ -311,25 +385,22 @@ also `conf-space-mode', `conf-colon-mode', `conf-javaprop-mode' and
                  ((looking-at ".*{"))          ; nop
                  ((setq space (1+ space))))
            (forward-line)))
-       (if (> jp (max unix win 3))
-           (conf-javaprop-mode)
-         (if (> colon (max equal space))
-             (conf-colon-mode)
-           (if (> space (max equal colon))
-               (conf-space-mode)
-             (if (or (> win unix)
-                     (and (= win unix) (eq system-type 'windows-nt)))
-                 (conf-windows-mode)
-               (conf-unix-mode))))))
+       (cond
+         ((> jp (max unix win 3)) (conf-javaprop-mode))
+         ((> colon (max equal space)) (conf-colon-mode))
+         ((> space (max equal colon)) (conf-space-mode))
+         ((or (> win unix) (and (= win unix) (eq system-type 'windows-nt)))
+          (conf-windows-mode))
+         (t (conf-unix-mode))))
+
     (kill-all-local-variables)
     (use-local-map conf-mode-map)
-
     (setq major-mode 'conf-mode
-         mode-name name)
-    (set (make-local-variable 'comment-start) comment)
-    (set (make-local-variable 'comment-start-skip)
-        (concat comment-start "+\\s *"))
-    (set (make-local-variable 'comment-use-syntax) t)
+         mode-name "Conf[?]")
+    (set (make-local-variable 'font-lock-defaults)
+         '(conf-font-lock-keywords nil t nil nil))
+    ;; Let newcomment.el decide this for itself.
+    ;; (set (make-local-variable 'comment-use-syntax) t)
     (set (make-local-variable 'parse-sexp-ignore-comments) t)
     (set (make-local-variable 'outline-regexp)
         "[ \t]*\\(?:\\[\\|.+[ \t\n]*{\\)")
@@ -337,39 +408,48 @@ also `conf-space-mode', `conf-colon-mode', `conf-javaprop-mode' and
         "[\n}]")
     (set (make-local-variable 'outline-level)
         'conf-outline-level)
-    (set-syntax-table syntax-table)
+    (set-syntax-table conf-mode-syntax-table)
     (setq imenu-generic-expression
          '(("Parameters" "^[ \t]*\\(.+?\\)[ \t]*=" 1)
            ;; [section]
            (nil "^[ \t]*\\[[ \t]*\\(.+\\)[ \t]*\\]" 1)
            ;; section { ... }
-           (nil "^[ \t]*\\([^=:\n]+\\)[ \t\n]*{" 1)))
-
+           (nil "^[ \t]*\\([^=:{} \t\n][^=:{}\n]+\\)[ \t\n]*{" 1)))
     (run-mode-hooks 'conf-mode-hook)))
 
+(defun conf-mode-initialize (comment &optional font-lock)
+  "Intitializations for sub-modes of conf-mode.
+COMMENT initializes `comment-start' and `comment-start-skip'.
+The optional arg FONT-LOCK is the value for FONT-LOCK-KEYWORDS."
+  (set (make-local-variable 'comment-start) comment)
+  (set (make-local-variable 'comment-start-skip)
+       (concat (regexp-quote comment-start) "+\\s *"))
+  (if font-lock
+      (set (make-local-variable 'font-lock-defaults)
+           `(,font-lock nil t nil nil))))
+
 ;;;###autoload
-(defun conf-unix-mode ()
+(define-derived-mode conf-unix-mode conf-mode "Conf[Unix]"
   "Conf Mode starter for Unix style Conf files.
 Comments start with `#'.
 For details see `conf-mode'.  Example:
 
-# Conf mode font-locks this right on Unix and with C-c C-u
+# Conf mode font-locks this right on Unix and with \\[conf-unix-mode]
 
 \[Desktop Entry]
         Encoding=UTF-8
         Name=The GIMP
         Name[ca]=El GIMP
         Name[cs]=GIMP"
-  (interactive)
-  (conf-mode "#" conf-unix-mode-syntax-table "Conf[Unix]"))
+  (conf-mode-initialize "#"))
 
 ;;;###autoload
-(defun conf-windows-mode ()
+(define-derived-mode conf-windows-mode conf-mode "Conf[WinIni]"
   "Conf Mode starter for Windows style Conf files.
 Comments start with `;'.
 For details see `conf-mode'.  Example:
 
-; Conf mode font-locks this right on Windows and with C-c C-w
+; Conf mode font-locks this right on Windows and with \\[conf-windows-mode]
 
 \[ExtShellFolderViews]
 Default={5984FFE0-28D4-11CF-AE66-08002B2E1262}
@@ -377,8 +457,7 @@ Default={5984FFE0-28D4-11CF-AE66-08002B2E1262}
 
 \[{5984FFE0-28D4-11CF-AE66-08002B2E1262}]
 PersistMoniker=file://Folder.htt"
-  (interactive)
-  (conf-mode ";" conf-mode-syntax-table "Conf[WinIni]"))
+  (conf-mode-initialize ";"))
 
 ;; Here are a few more or less widespread styles.  There are others, so
 ;; obscure, they are not covered.  E.g. RFC 2614 allows both Unix and Windows
@@ -386,13 +465,13 @@ PersistMoniker=file://Folder.htt"
 ;; if you need it.
 
 ;;;###autoload
-(defun conf-javaprop-mode ()
+(define-derived-mode conf-javaprop-mode conf-mode "Conf[JavaProp]"
   "Conf Mode starter for Java properties files.
 Comments start with `#' but are also recognized with `//' or
 between `/*' and `*/'.
 For details see `conf-mode'.  Example:
 
-# Conf mode font-locks this right with C-c C-j (Java properties)
+# Conf mode font-locks this right with \\[conf-javaprop-mode] (Java properties)
 // another kind of comment
 /* yet another */
 
@@ -402,31 +481,28 @@ name value
 x.1 =
 x.2.y.1.z.1 =
 x.2.y.1.z.2.zz ="
-  (interactive)
-  (conf-mode "#" conf-javaprop-mode-syntax-table "Conf[JavaProp]")
+  (conf-mode-initialize "#" 'conf-javaprop-font-lock-keywords)
   (set (make-local-variable 'conf-assignment-column)
        conf-javaprop-assignment-column)
   (set (make-local-variable 'conf-assignment-regexp)
        ".+?\\([ \t]*[=: \t][ \t]*\\|$\\)")
-  (set (make-local-variable 'conf-font-lock-keywords)
-       conf-javaprop-font-lock-keywords)
   (setq comment-start-skip "\\(?:#+\\|/[/*]+\\)\\s *")
   (setq imenu-generic-expression
        '(("Parameters" "^[ \t]*\\(.+?\\)[=: \t]" 1))))
 
 ;;;###autoload
-(defun conf-space-mode (&optional keywords)
+(define-derived-mode conf-space-mode conf-unix-mode "Conf[Space]"
   "Conf Mode starter for space separated conf files.
 \"Assignments\" are with ` '.  Keywords before the parameters are
-recognized according to `conf-space-keywords'.  Interactively
-with a prefix ARG of `0' no keywords will be recognized.  With
-any other prefix arg you will be prompted for a regexp to match
-the keywords.  Programmatically you can pass such a regexp as
-KEYWORDS, or any non-nil non-string for no keywords.
+recognized according to the variable `conf-space-keywords-alist'.
+Alternatively, you can specify a value for the file local variable
+`conf-space-keywords'.
+Use the function `conf-space-keywords' if you want to specify keywords
+in an interactive fashion instead.
 
 For details see `conf-mode'.  Example:
 
-# Conf mode font-locks this right with C-c C-s (space separated)
+# Conf mode font-locks this right with \\[conf-space-mode] (space separated)
 
 image/jpeg                     jpeg jpg jpe
 image/png                      png
@@ -437,58 +513,74 @@ class desktop
 # Standard multimedia devices
 add /dev/audio         desktop
 add /dev/mixer         desktop"
-  (interactive
-   (list (if current-prefix-arg
-            (if (> (prefix-numeric-value current-prefix-arg) 0)
-                (read-string "Regexp to match keywords: ")
-              t))))
-  (conf-unix-mode)
-  (setq mode-name "Conf[Space]")
-  (set (make-local-variable 'conf-assignment-sign)
-       nil)
-  (set (make-local-variable 'conf-font-lock-keywords)
-       conf-space-font-lock-keywords)
-  ;; This doesn't seem right, but the next two depend on conf-space-keywords
-  ;; being set, while after-change-major-mode-hook might set up imenu, needing
-  ;; the following result:
-  (hack-local-variables-prop-line)
-  (hack-local-variables)
-  (if keywords
-      (set (make-local-variable 'conf-space-keywords)
-          (if (stringp keywords) keywords))
-    (or conf-space-keywords
-       (not buffer-file-name)
-       (set (make-local-variable 'conf-space-keywords)
-            (assoc-default buffer-file-name conf-space-keywords-alist
-                           'string-match))))
-  (set (make-local-variable 'conf-assignment-regexp)
-       (if conf-space-keywords
-          (concat "\\(?:" conf-space-keywords "\\)[ \t]+.+?\\([ \t]+\\|$\\)")
-        ".+?\\([ \t]+\\|$\\)"))
+  (conf-mode-initialize "#" 'conf-space-font-lock-keywords)
+  (make-local-variable 'conf-assignment-sign)
+  (setq conf-assignment-sign nil)
+  (make-local-variable 'conf-space-keywords)
+  (cond (buffer-file-name
+        ;; We set conf-space-keywords directly, but a value which is
+        ;; in the local variables list or interactively specified
+        ;; (see the function conf-space-keywords) takes precedence.
+         (setq conf-space-keywords
+              (assoc-default buffer-file-name conf-space-keywords-alist
+                             'string-match))))
+  (conf-space-mode-internal)
+  ;; In case the local variables list specifies conf-space-keywords,
+  ;; recompute other things from that afterward.
+  (add-hook 'hack-local-variables-hook 'conf-space-mode-internal nil t))
+
+;;;###autoload
+(defun conf-space-keywords (keywords)
+  "Enter Conf Space mode using regexp KEYWORDS to match the keywords.
+See `conf-space-mode'."
+  (interactive "sConf Space keyword regexp: ")
+  (delay-mode-hooks
+    (conf-space-mode))
+  (if (string-equal keywords "")
+      (setq keywords nil))
+  (setq conf-space-keywords keywords)
+  (conf-space-mode-internal)
+  (run-mode-hooks))
+
+(defun conf-space-mode-internal ()
+  (make-local-variable 'conf-assignment-regexp)
+  (setq conf-assignment-regexp
+       (if conf-space-keywords
+           (concat "\\(?:" conf-space-keywords "\\)[ \t]+.+?\\([ \t]+\\|$\\)")
+         ".+?\\([ \t]+\\|$\\)"))
+  ;; If Font Lock is already enabled, reenable it with new
+  ;; conf-assignment-regexp.
+  (when (and font-lock-mode
+            (boundp 'font-lock-keywords)) ;see `normal-mode'
+    (font-lock-add-keywords nil nil)
+    (font-lock-mode 1))
+  ;; Copy so that we don't destroy shared structure.
+  (setq imenu-generic-expression (copy-sequence imenu-generic-expression))
+  ;; Get rid of any existing Parameters element.
+  (setq imenu-generic-expression
+       (delq (assoc "Parameters" imenu-generic-expression)
+             imenu-generic-expression))
+  ;; Add a new one based on conf-space-keywords.
   (setq imenu-generic-expression
-       `(,@(cdr imenu-generic-expression)
-         ("Parameters"
-          ,(if conf-space-keywords
-               (concat "^[ \t]*\\(?:" conf-space-keywords
-                       "\\)[ \t]+\\([^ \t\n]+\\)\\(?:[ \t]\\|$\\)")
-             "^[ \t]*\\([^ \t\n[]+\\)\\(?:[ \t]\\|$\\)")
-          1))))
+       (cons `("Parameters"
+               ,(if conf-space-keywords
+                    (concat "^[ \t]*\\(?:" conf-space-keywords
+                            "\\)[ \t]+\\([^ \t\n]+\\)\\(?:[ \t]\\|$\\)")
+                  "^[ \t]*\\([^ \t\n[]+\\)\\(?:[ \t]\\|$\\)")
+               1)
+             imenu-generic-expression)))
 
 ;;;###autoload
-(defun conf-colon-mode (&optional comment syntax-table name)
+(define-derived-mode conf-colon-mode conf-unix-mode "Conf[Colon]"
   "Conf Mode starter for Colon files.
 \"Assignments\" are with `:'.
 For details see `conf-mode'.  Example:
 
-# Conf mode font-locks this right with C-c C-c (colon)
+# Conf mode font-locks this right with \\[conf-colon-mode] (colon)
 
 <Multi_key> <exclam> <exclam>          : \"\\241\"     exclamdown
 <Multi_key> <c> <slash>                        : \"\\242\"     cent"
-  (interactive)
-  (if comment
-      (conf-mode comment syntax-table name)
-    (conf-unix-mode)
-    (setq mode-name "Conf[Colon]"))
+  (conf-mode-initialize "#" 'conf-colon-font-lock-keywords)
   (set (make-local-variable 'conf-assignment-space)
        conf-colon-assignment-space)
   (set (make-local-variable 'conf-assignment-column)
@@ -497,33 +589,35 @@ For details see `conf-mode'.  Example:
        ?:)
   (set (make-local-variable 'conf-assignment-regexp)
        ".+?\\([ \t]*:[ \t]*\\)")
-  (set (make-local-variable 'conf-font-lock-keywords)
-       conf-colon-font-lock-keywords)
   (setq imenu-generic-expression
        `(("Parameters" "^[ \t]*\\(.+?\\)[ \t]*:" 1)
          ,@(cdr imenu-generic-expression))))
 
 ;;;###autoload
-(defun conf-xdefaults-mode ()
+(define-derived-mode conf-ppd-mode conf-colon-mode "Conf[PPD]"
+  "Conf Mode starter for Adobe/CUPS PPD files.
+Comments start with `*%' and \"assignments\" are with `:'.
+For details see `conf-mode'.  Example:
+
+*% Conf mode font-locks this right with \\[conf-ppd-mode] (PPD)
+
+*DefaultTransfer: Null
+*Transfer Null.Inverse: \"{ 1 exch sub }\""
+  (conf-mode-initialize "*%")
+  ;; no sections, they match within PostScript code
+  (setq imenu-generic-expression (list (car imenu-generic-expression))))
+
+;;;###autoload
+(define-derived-mode conf-xdefaults-mode conf-colon-mode "Conf[Xdefaults]"
   "Conf Mode starter for Xdefaults files.
 Comments start with `!' and \"assignments\" are with `:'.
 For details see `conf-mode'.  Example:
 
-! Conf mode font-locks this right with C-c C-x (.Xdefaults)
+! Conf mode font-locks this right with \\[conf-xdefaults-mode] (.Xdefaults)
 
 *background:                   gray99
 *foreground:                   black"
-  (interactive)
-  (conf-colon-mode "!" conf-xdefaults-mode-syntax-table "Conf[Xdefaults]"))
-
-
-;; font lock support
-(if (boundp 'font-lock-defaults-alist)
-    (add-to-list
-     'font-lock-defaults-alist
-     (cons 'conf-mode
-          (list 'conf-font-lock-keywords nil t nil nil))))
-
+  (conf-mode-initialize "!"))
 
 (provide 'conf-mode)