*** empty log message ***
[gnus] / lisp / custom.el
1 ;;; custom.el --- User friendly customization support.
2
3 ;; Copyright (C) 1995, 1996 Free Software Foundation, Inc.
4
5 ;; Author: Per Abrahamsen <abraham@iesd.auc.dk>
6 ;; Keywords: help
7 ;; Version: 0.5
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 ;;; Commentary:
27
28 ;; WARNING: This package is still under construction and not all of
29 ;; the features below are implemented.
30 ;;
31 ;; This package provides a framework for adding user friendly
32 ;; customization support to Emacs.  Having to do customization by
33 ;; editing a text file in some arcane syntax is user hostile in the
34 ;; extreme, and to most users emacs lisp definitely count as arcane.
35 ;;
36 ;; The intent is that authors of emacs lisp packages declare the
37 ;; variables intended for user customization with `custom-declare'.
38 ;; Custom can then automatically generate a customization buffer with
39 ;; `custom-buffer-create' where the user can edit the package
40 ;; variables in a simple and intuitive way, as well as a menu with
41 ;; `custom-menu-create' where he can set the more commonly used
42 ;; variables interactively.
43 ;;
44 ;; It is also possible to use custom for modifying the properties of
45 ;; other objects than the package itself, by specifying extra optional
46 ;; arguments to `custom-buffer-create'.
47 ;;
48 ;; Custom is inspired by OPEN LOOK property windows.
49
50 ;;; Todo:  
51 ;;
52 ;; - Toggle documentation in three states `none', `one-line', `full'.
53 ;; - Function to generate an XEmacs menu from a CUSTOM.
54 ;; - Write TeXinfo documentation.
55 ;; - Make it possible to hide sections by clicking at the level.
56 ;; - Declare AUC TeX variables.
57 ;; - Declare (ding) Gnus variables.
58 ;; - Declare Emacs variables.
59 ;; - Implement remaining types.
60 ;; - XEmacs port.
61 ;; - Allow `URL', `info', and internal hypertext buttons.
62 ;; - Support meta-variables and goal directed customization.
63 ;; - Make it easy to declare custom types independently.
64 ;; - Make it possible to declare default value and type for a single
65 ;;   variable, storing the data in a symbol property.
66 ;; - Syntactic sugar for CUSTOM declarations.
67 ;; - Use W3 for variable documentation.
68
69 ;;; Code:
70
71 (eval-when-compile
72   (require 'cl))
73
74 ;;; Compatibility:
75
76 (defun custom-xmas-add-text-properties (start end props &optional object)
77   (add-text-properties start end props object)
78   (put-text-property start end 'start-open t object)
79   (put-text-property start end 'end-open t object))
80
81 (defun custom-xmas-put-text-property (start end prop value &optional object)
82   (put-text-property start end prop value object)
83   (put-text-property start end 'start-open t object)
84   (put-text-property start end 'end-open t object))
85
86 (defun custom-xmas-extent-start-open ()
87   (map-extents (lambda (extent arg)
88                  (set-extent-property extent 'start-open t))
89                nil (point) (min (1+ (point)) (point-max))))
90                   
91 (if (string-match "XEmacs\\|Lucid" emacs-version)
92     (progn
93       (fset 'custom-add-text-properties 'custom-xmas-add-text-properties)
94       (fset 'custom-put-text-property 'custom-xmas-put-text-property)
95       (fset 'custom-extent-start-open 'custom-xmas-extent-start-open)
96       (fset 'custom-set-text-properties
97             (if (fboundp 'set-text-properties)
98                 'set-text-properties))
99       (fset 'custom-buffer-substring-no-properties
100             (if (fboundp 'buffer-substring-no-properties)
101                 'buffer-substring-no-properties
102               'custom-xmas-buffer-substring-no-properties)))
103   (fset 'custom-add-text-properties 'add-text-properties)
104   (fset 'custom-put-text-property 'put-text-property)
105   (fset 'custom-extent-start-open 'ignore)
106   (fset 'custom-set-text-properties 'set-text-properties)
107   (fset 'custom-buffer-substring-no-properties 
108         'buffer-substring-no-properties))
109
110 (defun custom-xmas-buffer-substring-no-properties (beg end)
111   "Return the text from BEG to END, without text properties, as a string."
112   (let ((string (buffer-substring beg end)))
113     (custom-set-text-properties 0 (length string) nil string)
114     string))
115
116 (or (fboundp 'add-to-list)
117     ;; Introduced in Emacs 19.29.
118     (defun add-to-list (list-var element)
119       "Add to the value of LIST-VAR the element ELEMENT if it isn't there yet.
120 If you want to use `add-to-list' on a variable that is not defined
121 until a certain package is loaded, you should put the call to `add-to-list'
122 into a hook function that will be run only after loading the package.
123 `eval-after-load' provides one way to do this.  In some cases
124 other hooks, such as major mode hooks, can do the job."
125       (or (member element (symbol-value list-var))
126           (set list-var (cons element (symbol-value list-var))))))
127
128 (or (fboundp 'plist-get)
129     ;; Introduced in Emacs 19.29.
130     (defun plist-get (plist prop)
131       "Extract a value from a property list.
132 PLIST is a property list, which is a list of the form
133 \(PROP1 VALUE1 PROP2 VALUE2...).  This function returns the value
134 corresponding to the given PROP, or nil if PROP is not
135 one of the properties on the list."
136       (let (result)
137         (while plist
138           (if (eq (car plist) prop)
139               (setq result (car (cdr plist))
140                     plist nil)
141             (set plist (cdr (cdr plist)))))
142         result)))
143
144 (or (fboundp 'plist-put)
145     ;; Introduced in Emacs 19.29.
146     (defun plist-put (plist prop val)    
147       "Change value in PLIST of PROP to VAL.
148 PLIST is a property list, which is a list of the form
149 \(PROP1 VALUE1 PROP2 VALUE2 ...).  PROP is a symbol and VAL is any object.
150 If PROP is already a property on the list, its value is set to VAL,
151 otherwise the new PROP VAL pair is added.  The new plist is returned;
152 use `(setq x (plist-put x prop val))' to be sure to use the new value.
153 The PLIST is modified by side effects."
154       (if (null plist)
155           (list prop val)
156         (let ((current plist))
157           (while current
158             (cond ((eq (car current) prop)
159                    (setcar (cdr current) val)
160                    (setq current nil))
161                   ((null (cdr (cdr current)))
162                    (setcdr (cdr current) (list prop val))
163                    (setq current nil))
164                   (t
165                    (setq current (cdr (cdr current)))))))
166         plist)))
167
168 (or (fboundp 'match-string)
169     ;; Introduced in Emacs 19.29.
170     (defun match-string (num &optional string)
171   "Return string of text matched by last search.
172 NUM specifies which parenthesized expression in the last regexp.
173  Value is nil if NUMth pair didn't match, or there were less than NUM pairs.
174 Zero means the entire text matched by the whole regexp or whole string.
175 STRING should be given if the last search was by `string-match' on STRING."
176   (if (match-beginning num)
177       (if string
178           (substring string (match-beginning num) (match-end num))
179         (buffer-substring (match-beginning num) (match-end num))))))
180
181 (or (fboundp 'facep)
182     ;; Introduced in Emacs 19.29.
183     (defun facep (x)
184       "Return t if X is a face name or an internal face vector."
185       (and (or (and (fboundp 'internal-facep) (internal-facep x))
186                (and 
187                 (symbolp x) 
188                 (assq x (and (boundp 'global-face-data) global-face-data))))
189            t)))
190
191 ;; XEmacs and Emacs 19.29 facep does different things.
192 (if (fboundp 'find-face)
193     (fset 'custom-facep 'find-face)
194   (fset 'custom-facep 'facep))
195
196 (if (custom-facep 'underline)
197     ()
198   ;; No underline face in XEmacs 19.12.
199   (and (fboundp 'make-face)
200        (funcall (intern "make-face") 'underline))
201   ;; Must avoid calling set-face-underline-p directly, because it
202   ;; is a defsubst in emacs19, and will make the .elc files non
203   ;; portable!
204   (or (and (fboundp 'face-differs-from-default-p)
205            (face-differs-from-default-p 'underline))
206       (and (fboundp 'set-face-underline-p)
207            (funcall 'set-face-underline-p 'underline t))))
208
209 (defun custom-xmas-set-text-properties (start end props &optional buffer)
210   (if (null buffer)
211       (if props
212           (while props
213             (custom-put-text-property 
214              start end (car props) (nth 1 props) buffer)
215             (setq props (nthcdr 2 props)))
216         (remove-text-properties start end ()))))
217
218 (or (fboundp 'event-point)
219     ;; Missing in Emacs 19.29.
220     (defun event-point (event)
221       "Return the character position of the given mouse-motion, button-press,
222 or button-release event.  If the event did not occur over a window, or did
223 not occur over text, then this returns nil.  Otherwise, it returns an index
224 into the buffer visible in the event's window."
225       (posn-point (event-start event))))
226
227 (eval-when-compile
228   (defvar x-colors nil)
229   (defvar custom-button-face nil)
230   (defvar custom-field-uninitialized-face nil)
231   (defvar custom-field-invalid-face nil)
232   (defvar custom-field-modified-face nil)
233   (defvar custom-field-face nil)
234   (defvar custom-mouse-face nil)
235   (defvar custom-field-active-face nil))
236
237 ;; We can't easily check for a working intangible.
238 (defconst intangible (if (and (boundp 'emacs-minor-version)
239                               (or (> emacs-major-version 19)
240                                   (and (> emacs-major-version 18)
241                                        (> emacs-minor-version 28))))
242                          (setq intangible 'intangible)
243                        (setq intangible 'intangible-if-it-had-been-working))
244   "The symbol making text intangible.")
245
246 (defconst rear-nonsticky (if (string-match "XEmacs" emacs-version)
247                              'end-open
248                            'rear-nonsticky)
249   "The symbol making text properties non-sticky in the rear end.")
250
251 (defconst front-sticky (if (string-match "XEmacs" emacs-version)
252                            'front-closed
253                          'front-sticky)
254   "The symbol making text properties sticky in the front.")
255
256 (defconst mouse-face (if (string-match "XEmacs" emacs-version)
257                          'highlight
258                        'mouse-face)
259   "Symbol used for highlighting text under mouse.")
260
261 ;; Put it in the Help menu, if possible.
262 (if (string-match "XEmacs" emacs-version)
263     ;; XEmacs (disabled because it doesn't work)
264     (and current-menubar
265          (add-menu-item '("Help") "Customize..." 'customize t))
266   ;; Emacs 19.28 and earlier
267   (global-set-key [ menu-bar help customize ]
268                   '("Customize..." . customize))
269   ;; Emacs 19.29 and later
270   (global-set-key [ menu-bar help-menu customize ] 
271                   '("Customize..." . customize)))
272
273 ;; XEmacs popup-menu stolen from w3.el.
274 (defun custom-x-really-popup-menu (pos title menudesc)
275   "My hacked up function to do a blocking popup menu..."
276   (let ((echo-keystrokes 0)
277         event menu)
278     (while menudesc
279       (setq menu (cons (vector (car (car menudesc))
280                                (list (car (car menudesc))) t) menu)
281             menudesc (cdr menudesc)))
282     (setq menu (cons title menu))
283     (popup-menu menu)
284     (catch 'popup-done
285       (while t
286         (setq event (next-command-event event))
287         (cond ((and (misc-user-event-p event) (stringp (car-safe                                                   (event-object event))))
288                (throw 'popup-done (event-object event)))
289               ((and (misc-user-event-p event)
290                     (or (eq (event-object event) 'abort)
291                         (eq (event-object event) 'menu-no-selection-hook)))
292                nil)
293               ((not (popup-menu-up-p))
294                (throw 'popup-done nil))
295               ((button-release-event-p event);; don't beep twice
296                nil)
297               (t
298                (beep)
299                (message "please make a choice from the menu.")))))))
300
301 ;;; Categories:
302 ;;
303 ;; XEmacs use inheritable extents for the same purpose as Emacs uses
304 ;; the category text property.
305
306 (if (string-match "XEmacs" emacs-version)
307     (progn 
308       ;; XEmacs categories.
309       (defun custom-category-create (name)
310         (set name (make-extent nil nil))
311         "Create a text property category named NAME.")
312
313       (defun custom-category-put (name property value)
314         "In CATEGORY set PROPERTY to VALUE."
315         (set-extent-property (symbol-value name) property value))
316       
317       (defun custom-category-get (name property)
318         "In CATEGORY get PROPERTY."
319         (extent-property (symbol-value name) property))
320       
321       (defun custom-category-set (from to category)
322         "Make text between FROM and TWO have category CATEGORY."
323         (let ((extent (make-extent from to)))
324           (set-extent-parent extent (symbol-value category)))))
325       
326   ;; Emacs categories.
327   (defun custom-category-create (name)
328     "Create a text property category named NAME."
329     (set name name))
330
331   (defun custom-category-put (name property value)
332     "In CATEGORY set PROPERTY to VALUE."
333     (put name property value))
334
335   (defun custom-category-get (name property)
336     "In CATEGORY get PROPERTY."
337     (get name property))
338
339   (defun custom-category-set (from to category)
340     "Make text between FROM and TWO have category CATEGORY."
341     (custom-put-text-property from to 'category category)))
342
343 ;;; External Data:
344 ;; 
345 ;; The following functions and variables defines the interface for
346 ;; connecting a CUSTOM with an external entity, by default an emacs
347 ;; lisp variable.
348
349 (defvar custom-external 'default-value
350   "Function returning the external value of NAME.")
351
352 (defvar custom-external-set 'set-default
353   "Function setting the external value of NAME to VALUE.")
354
355 (defun custom-external (name)
356   "Get the external value associated with NAME."
357   (funcall custom-external name))
358
359 (defun custom-external-set (name value)
360   "Set the external value associated with NAME to VALUE."
361   (funcall custom-external-set name value))
362
363 (defvar custom-name-fields nil
364   "Alist of custom names and their associated editing field.")
365 (make-variable-buffer-local 'custom-name-fields)
366
367 (defun custom-name-enter (name field)
368   "Associate NAME with FIELD."
369   (if (null name)
370       ()
371     (custom-assert 'field)
372     (setq custom-name-fields (cons (cons name field) custom-name-fields))))
373
374 (defun custom-name-field (name)
375   "The editing field associated with NAME."
376   (cdr (assq name custom-name-fields)))
377
378 (defun custom-name-value (name)
379   "The value currently displayed for NAME in the customization buffer."
380   (let* ((field (custom-name-field name))
381          (custom (custom-field-custom field)))
382     (custom-field-parse field)
383     (funcall (custom-property custom 'export) custom
384              (car (custom-field-extract custom field)))))
385
386 (defvar custom-save 'custom-save
387   "Function that will save current customization buffer.")
388
389 ;;; Custom Functions:
390 ;;
391 ;; The following functions are part of the public interface to the
392 ;; CUSTOM datastructure.  Each CUSTOM describes a group of variables,
393 ;; a single variable, or a component of a structured variable.  The
394 ;; CUSTOM instances are part of two hierarchies, the first is the
395 ;; `part-of' hierarchy in which each CUSTOM is a component of another
396 ;; CUSTOM, except for the top level CUSTOM which is contained in
397 ;; `custom-data'.  The second hierarchy is a `is-a' type hierarchy
398 ;; where each CUSTOM is a leaf in the hierarchy defined by the `type'
399 ;; property and `custom-type-properties'.
400
401 (defvar custom-file "~/.custom.el"
402   "Name of file with customization information.")
403
404 (defconst custom-data
405   '((tag . "Emacs")
406     (doc . "The extensible self-documenting text editor.")
407     (type . group)
408     (data "\n"
409           ((header . nil)
410            (compact . t)
411            (type . group)
412            (doc . "\
413 Press [Save] to save any changes permanently after you are done editing.  
414 You can load customization information from other files by editing the
415 `File' field and pressing the [Load] button.  When you press [Save] the
416 customization information of all files you have loaded, plus any
417 changes you might have made manually, will be stored in the file 
418 specified by the `File' field.")
419            (data ((tag . "Load")
420                   (type . button)
421                   (query . custom-load))
422                  ((tag . "Save")
423                   (type . button)
424                   (query . custom-save))
425                  ((name . custom-file)
426                   (default . "~/.custom.el")
427                   (doc . "Name of file with customization information.\n")
428                   (tag . "File")
429                   (type . file))))))
430   "The global customization information.  
431 A custom association list.")
432
433 (defun custom-declare (path custom)
434   "Declare variables for customization.  
435 PATH is a list of tags leading to the place in the customization
436 hierarchy the new entry should be added.  CUSTOM is the entry to add."
437   (custom-initialize custom)
438   (let ((current (custom-travel-path custom-data path)))
439     (or (member custom (custom-data current))
440         (nconc (custom-data current) (list custom)))))
441
442 (put 'custom-declare 'lisp-indent-hook 1)
443
444 (defconst custom-type-properties
445   '((repeat (type . default)
446             ;; See `custom-match'.
447             (import . custom-repeat-import)
448             (eval . custom-repeat-eval)
449             (quote . custom-repeat-quote)
450             (accept . custom-repeat-accept)
451             (extract . custom-repeat-extract)
452             (validate . custom-repeat-validate)
453             (insert . custom-repeat-insert)
454             (match . custom-repeat-match)
455             (query . custom-repeat-query)
456             (prefix . "")
457             (del-tag . "[DEL]")
458             (add-tag . "[INS]"))
459     (pair (type . group)
460           ;; A cons-cell.
461           (accept . custom-pair-accept)
462           (eval . custom-pair-eval)
463           (import . custom-pair-import)
464           (quote . custom-pair-quote)
465           (valid . (lambda (c d) (consp d)))
466           (extract . custom-pair-extract))
467     (list (type . group)
468           ;; A lisp list.
469           (quote . custom-list-quote)
470           (valid . (lambda (c d)
471                      (listp d)))
472           (extract . custom-list-extract))
473     (group (type . default)
474            ;; See `custom-match'.
475            (face-tag . nil)
476            (eval . custom-group-eval)
477            (import . custom-group-import)
478            (initialize . custom-group-initialize)
479            (apply . custom-group-apply)
480            (reset . custom-group-reset)
481            (factory-reset . custom-group-factory-reset)
482            (extract . nil)
483            (validate . custom-group-validate)
484            (query . custom-toggle-hide)
485            (accept . custom-group-accept)
486            (insert . custom-group-insert)
487            (find . custom-group-find))
488     (toggle (type . choice)
489             ;; Booleans.
490             (data ((type . const)
491                    (tag . "On ")
492                    (default . t))
493                   ((type . const)
494                    (tag . "Off")
495                    (default . nil))))
496     (triggle (type . choice)
497              ;; On/Off/Default.
498              (data ((type . const)
499                     (tag . "On ")
500                     (default . t))
501                    ((type . const)
502                     (tag . "Off")
503                     (default . nil))
504                    ((type . const)
505                     (tag . "Def")
506                     (default . custom:asis))))
507     (choice (type . default)
508             ;; See `custom-match'.
509             (query . custom-choice-query)
510             (accept . custom-choice-accept)
511             (extract . custom-choice-extract)
512             (validate . custom-choice-validate)
513             (insert . custom-choice-insert)
514             (none (tag . "Unknown")
515                   (default . __uninitialized__)
516                   (type . const)))
517     (const (type . default)
518            ;; A `const' only matches a single lisp value.
519            (extract . (lambda (c f) (list (custom-default c))))
520            (validate . (lambda (c f) nil))
521            (valid . custom-const-valid)