Fix maintainer address.
[bpt/emacs.git] / lisp / enriched.el
CommitLineData
be010748 1;;; enriched.el --- read and save files in text/enriched format
b578f267 2
312cac0e 3;; Copyright (c) 1994, 1995, 1996 Free Software Foundation, Inc.
0122281a 4
5762abec 5;; Author: Boris Goldowsky <boris@gnu.org>
0122281a
BG
6;; Keywords: wp, faces
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
b578f267 14
0122281a
BG
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
b578f267 19
0122281a 20;; You should have received a copy of the GNU General Public License
b578f267
EN
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
0122281a
BG
24
25;;; Commentary:
b578f267 26
0122281a 27;; This file implements reading, editing, and saving files with
b578f267
EN
28;; text-properties such as faces, levels of indentation, and true line
29;; breaks distinguished from newlines just used to fit text into the window.
30
0122281a 31;; The file format used is the MIME text/enriched format, which is a
b578f267
EN
32;; standard format defined in internet RFC 1563. All standard annotations
33;; are supported except for <smaller> and <bigger>, which are currently not
0122281a 34;; possible to display.
b578f267 35
0122281a 36;; A separate file, enriched.doc, contains further documentation and other
b578f267
EN
37;; important information about this code. It also serves as an example
38;; file in text/enriched format. It should be in the etc directory of your
39;; emacs distribution.
40
41;;; Code:
0122281a
BG
42
43(provide 'enriched)
0122281a
BG
44
45;;;
46;;; Variables controlling the display
47;;;
48
20f0de75
RS
49(defgroup enriched nil
50 "Read and save files in text/enriched format"
51 :group 'wp)
52
53(defcustom enriched-verbose t
54 "*If non-nil, give status messages when reading and writing files."
55 :type 'boolean
56 :group 'enriched)
0122281a 57
0122281a
BG
58;;;
59;;; Set up faces & display table
60;;;
61
8d86541f
RS
62;; Emacs doesn't have a "fixed" face by default, since all faces currently
63;; have to be fixed-width. So we just pick one that looks different from the
64;; default.
65(defface fixed
66 '((t (:bold t)))
67 "Face used for text that must be shown in fixed width.
68Currently, emacs can only display fixed-width fonts, but this may change.
69This face is used for text specifically marked as fixed-width, for example
70in text/enriched files."
71 :group 'enriched)
72
73(defface excerpt
74 '((t (:italic t)))
75 "Face used for text that is an excerpt from another document.
76This is used in enriched-mode for text explicitly marked as an excerpt."
77 :group 'enriched)
0122281a 78
b2f51b24
BG
79(defconst enriched-display-table (or (copy-sequence standard-display-table)
80 (make-display-table)))
0122281a
BG
81(aset enriched-display-table ?\f (make-vector (1- (frame-width)) ?-))
82
b2f51b24 83(defconst enriched-par-props '(left-margin right-margin justification)
0122281a
BG
84 "Text-properties that usually apply to whole paragraphs.
85These are set front-sticky everywhere except at hard newlines.")
86
87;;;
88;;; Variables controlling the file format
89;;; (bidirectional)
90
b2f51b24 91(defconst enriched-initial-annotation
0122281a 92 (lambda ()
b2f51b24 93 (format "Content-Type: text/enriched\nText-Width: %d\n\n"
8b914003 94 fill-column))
0122281a
BG
95 "What to insert at the start of a text/enriched file.
96If this is a string, it is inserted. If it is a list, it should be a lambda
97expression, which is evaluated to get the string to insert.")
98
b2f51b24 99(defconst enriched-annotation-format "<%s%s>"
0122281a
BG
100 "General format of enriched-text annotations.")
101
b2f51b24 102(defconst enriched-annotation-regexp "<\\(/\\)?\\([-A-za-z0-9]+\\)>"
0122281a
BG
103 "Regular expression matching enriched-text annotations.")
104
b2f51b24 105(defconst enriched-translations
0122281a
BG
106 '((face (bold-italic "bold" "italic")
107 (bold "bold")
108 (italic "italic")
109 (underline "underline")
110 (fixed "fixed")
111 (excerpt "excerpt")
112 (default )
113 (nil enriched-encode-other-face))
0122281a
BG
114 (left-margin (4 "indent"))
115 (right-margin (4 "indentright"))
116 (justification (none "nofill")
117 (right "flushright")
118 (left "flushleft")
5f329f43 119 (full "flushboth")
0122281a
BG
120 (center "center"))
121 (PARAMETER (t "param")) ; Argument of preceding annotation
122 ;; The following are not part of the standard:
123 (FUNCTION (enriched-decode-foreground "x-color")
d9e28c1c
GM
124 (enriched-decode-background "x-bg-color")
125 (enriched-decode-display-prop "x-display"))
0122281a 126 (read-only (t "x-read-only"))
d9e28c1c 127 (display (nil enriched-handle-display-prop))
b2f51b24 128 (unknown (nil format-annotate-value))
0122281a
BG
129; (font-size (2 "bigger") ; unimplemented
130; (-2 "smaller"))
131)
132 "List of definitions of text/enriched annotations.
b2f51b24
BG
133See `format-annotate-region' and `format-deannotate-region' for the definition
134of this structure.")
0122281a 135
b2f51b24
BG
136(defconst enriched-ignore
137 '(front-sticky rear-nonsticky hard)
138 "Properties that are OK to ignore when saving text/enriched files.
139Any property that is neither on this list nor dealt with by
140`enriched-translations' will generate a warning.")
0122281a
BG
141
142;;; Internal variables
143
144(defvar enriched-mode nil
2806002f 145 "True if Enriched mode is in use.")
0122281a 146(make-variable-buffer-local 'enriched-mode)
0a3e74ce 147(put 'enriched-mode 'permanent-local t)
0122281a
BG
148
149(if (not (assq 'enriched-mode minor-mode-alist))
150 (setq minor-mode-alist
151 (cons '(enriched-mode " Enriched")
152 minor-mode-alist)))
153
20f0de75 154(defcustom enriched-mode-hook nil
2806002f 155 "Functions to run when entering Enriched mode.
0122281a 156If you set variables in this hook, you should arrange for them to be restored
2806002f 157to their old values if you leave Enriched mode. One way to do this is to add
20f0de75
RS
158them and their old values to `enriched-old-bindings'."
159 :type 'hook
160 :group 'enriched)
0122281a
BG
161
162(defvar enriched-old-bindings nil
163 "Store old variable values that we change when entering mode.
164The value is a list of \(VAR VALUE VAR VALUE...).")
165(make-variable-buffer-local 'enriched-old-bindings)
166
0122281a
BG
167;;;
168;;; Define the mode
169;;;
170
60d15bc7 171;;;###autoload
b2f51b24 172(defun enriched-mode (&optional arg)
0122281a
BG
173 "Minor mode for editing text/enriched files.
174These are files with embedded formatting information in the MIME standard
175text/enriched format.
2806002f 176Turning the mode on runs `enriched-mode-hook'.
0122281a 177
2806002f 178More information about Enriched mode is available in the file
0122281a
BG
179etc/enriched.doc in the Emacs distribution directory.
180
181Commands:
182
183\\<enriched-mode-map>\\{enriched-mode-map}"
184 (interactive "P")
185 (let ((mod (buffer-modified-p)))
186 (cond ((or (<= (prefix-numeric-value arg) 0)
187 (and enriched-mode (null arg)))
188 ;; Turn mode off
189 (setq enriched-mode nil)
b2f51b24 190 (setq buffer-file-format (delq 'text/enriched buffer-file-format))
0122281a
BG
191 ;; restore old variable values
192 (while enriched-old-bindings
193 (funcall 'set (car enriched-old-bindings)
194 (car (cdr enriched-old-bindings)))
b2f51b24 195 (setq enriched-old-bindings (cdr (cdr enriched-old-bindings)))))
0122281a 196
b2f51b24 197 (enriched-mode nil) ; Mode already on; do nothing.
0122281a 198
b2f51b24 199 (t (setq enriched-mode t) ; Turn mode on
8b914003 200 (add-to-list 'buffer-file-format 'text/enriched)
b2f51b24 201 ;; Save old variable values before we change them.
2806002f 202 ;; These will be restored if we exit Enriched mode.
b2f51b24
BG
203 (setq enriched-old-bindings
204 (list 'buffer-display-table buffer-display-table
205 'indent-line-function indent-line-function
ef6fe31b 206 'default-text-properties default-text-properties))
b2f51b24 207 (make-local-variable 'indent-line-function)
ef6fe31b 208 (make-local-variable 'default-text-properties)
b2f51b24 209 (setq indent-line-function 'indent-to-left-margin
8b914003
RS
210 buffer-display-table enriched-display-table)
211 (use-hard-newlines 1 nil)
ef6fe31b 212 (let ((sticky (plist-get default-text-properties 'front-sticky))
b2f51b24
BG
213 (p enriched-par-props))
214 (while p
8b914003 215 (add-to-list 'sticky (car p))
b2f51b24
BG
216 (setq p (cdr p)))
217 (if sticky
ef6fe31b
BG
218 (setq default-text-properties
219 (plist-put default-text-properties
220 'front-sticky sticky))))
2806002f 221 (run-hooks 'enriched-mode-hook)))
0122281a
BG
222 (set-buffer-modified-p mod)
223 (force-mode-line-update)))
224
225;;;
226;;; Keybindings
227;;;
228
229(defvar enriched-mode-map nil
2806002f 230 "Keymap for Enriched mode.")
0122281a
BG
231
232(if (null enriched-mode-map)
233 (fset 'enriched-mode-map (setq enriched-mode-map (make-sparse-keymap))))
234
235(if (not (assq 'enriched-mode minor-mode-map-alist))
236 (setq minor-mode-map-alist
237 (cons (cons 'enriched-mode enriched-mode-map)
238 minor-mode-map-alist)))
239
b2f51b24
BG
240(define-key enriched-mode-map "\C-a" 'beginning-of-line-text)
241(define-key enriched-mode-map "\C-m" 'reindent-then-newline-and-indent)
242(define-key enriched-mode-map "\C-j" 'reindent-then-newline-and-indent)
243(define-key enriched-mode-map "\M-j" 'facemenu-justification-menu)
5f329f43 244(define-key enriched-mode-map "\M-S" 'set-justification-center)
b2f51b24 245(define-key enriched-mode-map "\C-x\t" 'increase-left-margin)
5f329f43
RS
246(define-key enriched-mode-map "\C-c\C-l" 'set-left-margin)
247(define-key enriched-mode-map "\C-c\C-r" 'set-right-margin)
0122281a
BG
248
249;;;
b2f51b24 250;;; Some functions dealing with text-properties, especially indentation
0122281a
BG
251;;;
252
0122281a
BG
253(defun enriched-map-property-regions (prop func &optional from to)
254 "Apply a function to regions of the buffer based on a text property.
255For each contiguous region of the buffer for which the value of PROPERTY is
256eq, the FUNCTION will be called. Optional arguments FROM and TO specify the
257region over which to scan.
258
259The specified function receives three arguments: the VALUE of the property in
260the region, and the START and END of each region."
261 (save-excursion
262 (save-restriction
263 (if to (narrow-to-region (point-min) to))
264 (goto-char (or from (point-min)))
265 (let ((begin (point))
266 end
267 (marker (make-marker))
268 (val (get-text-property (point) prop)))
269 (while (setq end (text-property-not-all begin (point-max) prop val))
270 (move-marker marker end)
271 (funcall func val begin (marker-position marker))
272 (setq begin (marker-position marker)
273 val (get-text-property marker prop)))
274 (if (< begin (point-max))
275 (funcall func val begin (point-max)))))))
276
277(put 'enriched-map-property-regions 'lisp-indent-hook 1)
278
0122281a
BG
279(defun enriched-insert-indentation (&optional from to)
280 "Indent and justify each line in the region."
281 (save-excursion
282 (save-restriction
283 (if to (narrow-to-region (point-min) to))
284 (goto-char (or from (point-min)))
285 (if (not (bolp)) (forward-line 1))
286 (while (not (eobp))
b2f51b24
BG
287 (if (eolp)
288 nil ; skip blank lines
289 (indent-to (current-left-margin))
290 (justify-current-line t nil t))
0122281a
BG
291 (forward-line 1)))))
292
0122281a 293;;;
b2f51b24 294;;; Encoding Files
0122281a
BG
295;;;
296
b2f51b24 297;;;###autoload
8b914003 298(defun enriched-encode (from to orig-buf)
0122281a 299 (if enriched-verbose (message "Enriched: encoding document..."))
b2f51b24
BG
300 (save-restriction
301 (narrow-to-region from to)
302 (delete-to-left-margin)
303 (unjustify-region)
304 (goto-char from)
305 (format-replace-strings '(("<" . "<<")))
306 (format-insert-annotations
307 (format-annotate-region from (point-max) enriched-translations
308 'enriched-make-annotation enriched-ignore))
309 (goto-char from)
310 (insert (if (stringp enriched-initial-annotation)
311 enriched-initial-annotation
8b914003
RS
312 (save-excursion
313 ;; Eval this in the buffer we are annotating. This
314 ;; fixes a bug which was saving incorrect File-Width
315 ;; information, since we were looking at local
316 ;; variables in the wrong buffer.
317 (if orig-buf (set-buffer orig-buf))
318 (funcall enriched-initial-annotation))))
b2f51b24
BG
319 (enriched-map-property-regions 'hard
320 (lambda (v b e)
321 (if (and v (= ?\n (char-after b)))
322 (progn (goto-char b) (insert "\n"))))
323 (point) nil)
324 (if enriched-verbose (message nil))
325 ;; Return new end.
326 (point-max)))
0122281a 327
b2f51b24
BG
328(defun enriched-make-annotation (name positive)
329 "Format an annotation called NAME.
330If POSITIVE is non-nil, this is the opening annotation, if nil, this is the
331matching close."
332 (cond ((stringp name)
333 (format enriched-annotation-format (if positive "" "/") name))
334 ;; Otherwise it is an annotation with parameters, represented as a list
335 (positive
336 (let ((item (car name))
337 (params (cdr name)))
338 (concat (format enriched-annotation-format "" item)
339 (mapconcat (lambda (i) (concat "<param>" i "</param>"))
340 params ""))))
341 (t (format enriched-annotation-format "/" (car name)))))
0122281a 342
b2f51b24
BG
343(defun enriched-encode-other-face (old new)
344 "Generate annotations for random face change.
345One annotation each for foreground color, background color, italic, etc."
346 (cons (and old (enriched-face-ans old))
347 (and new (enriched-face-ans new))))
348
349(defun enriched-face-ans (face)
350 "Return annotations specifying FACE."
351 (cond ((string-match "^fg:" (symbol-name face))
352 (list (list "x-color" (substring (symbol-name face) 3))))
353 ((string-match "^bg:" (symbol-name face))
354 (list (list "x-bg-color" (substring (symbol-name face) 3))))
355 ((let* ((fg (face-foreground face))
356 (bg (face-background face))
357 (props (face-font face t))
358 (ans (cdr (format-annotate-single-property-change
359 'face nil props enriched-translations))))
360 (if fg (setq ans (cons (list "x-color" fg) ans)))
361 (if bg (setq ans (cons (list "x-bg-color" bg) ans)))
362 ans))))
0122281a
BG
363
364;;;
b2f51b24 365;;; Decoding files
0122281a
BG
366;;;
367
b2f51b24
BG
368;;;###autoload
369(defun enriched-decode (from to)
0122281a 370 (if enriched-verbose (message "Enriched: decoding document..."))
8b914003 371 (use-hard-newlines 1 'never)
0122281a
BG
372 (save-excursion
373 (save-restriction
b2f51b24
BG
374 (narrow-to-region from to)
375 (goto-char from)
8b914003
RS
376
377 ;; Deal with header
378 (let ((file-width (enriched-get-file-width)))
b2f51b24
BG
379 (enriched-remove-header)
380
381 ;; Deal with newlines
b2f51b24
BG
382 (while (search-forward-regexp "\n\n+" nil t)
383 (if (current-justification)
384 (delete-char -1))
8b914003 385 (set-hard-newline-properties (match-beginning 0) (point)))
b2f51b24
BG
386
387 ;; Translate annotations
388 (format-deannotate-region from (point-max) enriched-translations
389 'enriched-next-annotation)
390
8b914003
RS
391 ;; Indent or fill the buffer
392 (cond (file-width ; File was filled to this width
393 (setq fill-column file-width)
394 (if enriched-verbose (message "Indenting..."))
395 (enriched-insert-indentation))
396 (t ; File was not filled.
397 (if enriched-verbose (message "Filling paragraphs..."))
398 (fill-region (point-min) (point-max))))
399 (if enriched-verbose (message nil)))
b2f51b24
BG
400 (point-max))))
401
402(defun enriched-next-annotation ()
403 "Find and return next text/enriched annotation.
09291989 404Any \"<<\" strings encountered are converted to \"<\".
b2f51b24
BG
405Return value is \(begin end name positive-p), or nil if none was found."
406 (while (and (search-forward "<" nil 1)
407 (progn (goto-char (match-beginning 0))
408 (not (looking-at enriched-annotation-regexp))))
409 (forward-char 1)
410 (if (= ?< (char-after (point)))
411 (delete-char 1)
412 ;; A single < that does not start an annotation is an error,
413 ;; which we note and then ignore.
a59d9084
KH
414 (message "Warning: malformed annotation in file at %s"
415 (1- (point)))))
b2f51b24
BG
416 (if (not (eobp))
417 (let* ((beg (match-beginning 0))
418 (end (match-end 0))
419 (name (downcase (buffer-substring
420 (match-beginning 2) (match-end 2))))
421 (pos (not (match-beginning 1))))
422 (list beg end name pos))))
0122281a
BG
423
424(defun enriched-get-file-width ()
425 "Look for file width information on this line."
426 (save-excursion
b2f51b24 427 (if (search-forward "Text-Width: " (+ (point) 1000) t)
0122281a
BG
428 (read (current-buffer)))))
429
b2f51b24
BG
430(defun enriched-remove-header ()
431 "Remove file-format header at point."
432 (while (looking-at "^[-A-Za-z]+: .*\n")
433 (delete-region (point) (match-end 0)))
434 (if (looking-at "^\n")
435 (delete-char 1)))
436
7ddb5524 437(defun enriched-decode-foreground (from to &optional color)
b2f51b24 438 (let ((face (intern (concat "fg:" color))))
7ddb5524
RS
439 (cond ((null color)
440 (message "Warning: no color specified for <x-color>"))
441 ((internal-find-face face))
b2f51b24 442 ((and window-system (facemenu-get-face face)))
b2f51b24 443 ((make-face face)
7ddb5524 444 (message "Warning: color `%s' can't be displayed" color)))
b2f51b24
BG
445 (list from to 'face face)))
446
7ddb5524 447(defun enriched-decode-background (from to &optional color)
b2f51b24 448 (let ((face (intern (concat "bg:" color))))
7ddb5524
RS
449 (cond ((null color)
450 (message "Warning: no color specified for <x-bg-color>"))
451 ((internal-find-face face))
b2f51b24 452 ((and window-system (facemenu-get-face face)))
b2f51b24 453 ((make-face face)
7ddb5524 454 (message "Warning: color `%s' can't be displayed" color)))
b2f51b24 455 (list from to 'face face)))
0122281a 456
d9e28c1c
GM
457
458\f
459;;; Handling the `display' property.
460
461
462(defun enriched-handle-display-prop (old new)
463 "Return a list of annotations for a change in the `display' property.
464OLD is the old value of the property, NEW is the new value. Value
465is a list `(CLOSE OPEN)', where CLOSE is a list of annotations to
466close and OPEN a list of annotations to open. Each of these lists
467has the form `(ANNOTATION PARAM ...)'."
468 (let ((annotation "x-display")
469 (param (prin1-to-string (or old new)))
470 close open)
471 (if (null old)
472 (list nil (list annotation param))
473 (list (list annotation param)))))
474
475
476(defun enriched-decode-display-prop (start end &optional param)
477 "Decode a `display' property for text between START and END.
478PARAM is a `<param>' found for the property.
479Value is a list `(START END SYMBOL VALUE)' with START and END denoting
480the range of text to assign text property SYMBOL with value VALUE "
481 (let ((prop (when (stringp param)
482 (condition-case ()
483 (car (read-from-string param))
484 (error nil)))))
485 (unless prop
486 (message "Warning: invalid <x-display> parameter %s" param))
487 (list start end 'display prop)))
488
489
0122281a 490;;; enriched.el ends here