*** empty log message ***
[bpt/emacs.git] / lisp / enriched.el
CommitLineData
be010748 1;;; enriched.el --- read and save files in text/enriched format
b578f267 2
2b2ba4ef 3;; Copyright (c) 1994, 1995, 1996, 2002 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
58b64ac7 66 '((t (:weight bold)))
8d86541f
RS
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
58b64ac7 74 '((t (:slant italic)))
8d86541f 75 "Face used for text that is an excerpt from another document.
6b669257 76This is used in Enriched mode for text explicitly marked as an excerpt."
8d86541f 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
1fb38620 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")
2e4c0487 120 (center "center"))
0122281a
BG
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
0122281a 144
20f0de75 145(defcustom enriched-mode-hook nil
6c0bc4a0 146 "Hook run after entering/leaving Enriched mode.
0122281a 147If you set variables in this hook, you should arrange for them to be restored
2806002f 148to their old values if you leave Enriched mode. One way to do this is to add
20f0de75
RS
149them and their old values to `enriched-old-bindings'."
150 :type 'hook
151 :group 'enriched)
0122281a
BG
152
153(defvar enriched-old-bindings nil
154 "Store old variable values that we change when entering mode.
155The value is a list of \(VAR VALUE VAR VALUE...).")
156(make-variable-buffer-local 'enriched-old-bindings)
157
0122281a
BG
158;;;
159;;; Define the mode
160;;;
161
6c0bc4a0 162(put 'enriched-mode 'permanent-local t)
60d15bc7 163;;;###autoload
6c0bc4a0 164(define-minor-mode enriched-mode
0122281a
BG
165 "Minor mode for editing text/enriched files.
166These are files with embedded formatting information in the MIME standard
167text/enriched format.
2806002f 168Turning the mode on runs `enriched-mode-hook'.
0122281a 169
2e4c0487 170More information about Enriched mode is available in the file
6c0bc4a0 171etc/enriched.doc in the Emacs distribution directory.
0122281a
BG
172
173Commands:
174
97f51df1 175\\{enriched-mode-map}"
6c0bc4a0
SM
176 nil " Enriched" nil
177 (cond ((null enriched-mode)
178 ;; Turn mode off
179 (setq buffer-file-format (delq 'text/enriched buffer-file-format))
180 ;; restore old variable values
181 (while enriched-old-bindings
182 (set (pop enriched-old-bindings) (pop enriched-old-bindings))))
2e4c0487 183
6c0bc4a0
SM
184 ((memq 'text/enriched buffer-file-format)
185 ;; Mode already on; do nothing.
186 nil)
187
188 (t ; Turn mode on
189 (push 'text/enriched buffer-file-format)
190 ;; Save old variable values before we change them.
191 ;; These will be restored if we exit Enriched mode.
192 (setq enriched-old-bindings
193 (list 'buffer-display-table buffer-display-table
194 'indent-line-function indent-line-function
195 'default-text-properties default-text-properties))
196 (make-local-variable 'indent-line-function)
197 (make-local-variable 'default-text-properties)
198 (setq indent-line-function 'indent-to-left-margin ;WHY?? -sm
199 buffer-display-table enriched-display-table)
200 (use-hard-newlines 1 nil)
201 (let ((sticky (plist-get default-text-properties 'front-sticky))
202 (p enriched-par-props))
203 (dolist (x p)
204 (add-to-list 'sticky x))
205 (if sticky
206 (setq default-text-properties
207 (plist-put default-text-properties
208 'front-sticky sticky)))))))
0122281a
BG
209
210;;;
211;;; Keybindings
212;;;
213
214(defvar enriched-mode-map nil
2806002f 215 "Keymap for Enriched mode.")
0122281a
BG
216
217(if (null enriched-mode-map)
218 (fset 'enriched-mode-map (setq enriched-mode-map (make-sparse-keymap))))
219
220(if (not (assq 'enriched-mode minor-mode-map-alist))
221 (setq minor-mode-map-alist
222 (cons (cons 'enriched-mode enriched-mode-map)
223 minor-mode-map-alist)))
224
b2f51b24
BG
225(define-key enriched-mode-map "\C-a" 'beginning-of-line-text)
226(define-key enriched-mode-map "\C-m" 'reindent-then-newline-and-indent)
227(define-key enriched-mode-map "\C-j" 'reindent-then-newline-and-indent)
228(define-key enriched-mode-map "\M-j" 'facemenu-justification-menu)
5f329f43 229(define-key enriched-mode-map "\M-S" 'set-justification-center)
b2f51b24 230(define-key enriched-mode-map "\C-x\t" 'increase-left-margin)
5f329f43
RS
231(define-key enriched-mode-map "\C-c\C-l" 'set-left-margin)
232(define-key enriched-mode-map "\C-c\C-r" 'set-right-margin)
0122281a
BG
233
234;;;
b2f51b24 235;;; Some functions dealing with text-properties, especially indentation
0122281a
BG
236;;;
237
0122281a
BG
238(defun enriched-map-property-regions (prop func &optional from to)
239 "Apply a function to regions of the buffer based on a text property.
240For each contiguous region of the buffer for which the value of PROPERTY is
241eq, the FUNCTION will be called. Optional arguments FROM and TO specify the
242region over which to scan.
243
244The specified function receives three arguments: the VALUE of the property in
245the region, and the START and END of each region."
246 (save-excursion
247 (save-restriction
248 (if to (narrow-to-region (point-min) to))
249 (goto-char (or from (point-min)))
250 (let ((begin (point))
251 end
252 (marker (make-marker))
253 (val (get-text-property (point) prop)))
254 (while (setq end (text-property-not-all begin (point-max) prop val))
255 (move-marker marker end)
256 (funcall func val begin (marker-position marker))
257 (setq begin (marker-position marker)
258 val (get-text-property marker prop)))
259 (if (< begin (point-max))
260 (funcall func val begin (point-max)))))))
261
262(put 'enriched-map-property-regions 'lisp-indent-hook 1)
263
0122281a
BG
264(defun enriched-insert-indentation (&optional from to)
265 "Indent and justify each line in the region."
266 (save-excursion
267 (save-restriction
268 (if to (narrow-to-region (point-min) to))
269 (goto-char (or from (point-min)))
270 (if (not (bolp)) (forward-line 1))
271 (while (not (eobp))
b2f51b24
BG
272 (if (eolp)
273 nil ; skip blank lines
274 (indent-to (current-left-margin))
275 (justify-current-line t nil t))
0122281a
BG
276 (forward-line 1)))))
277
0122281a 278;;;
b2f51b24 279;;; Encoding Files
0122281a
BG
280;;;
281
b2f51b24 282;;;###autoload
8b914003 283(defun enriched-encode (from to orig-buf)
0122281a 284 (if enriched-verbose (message "Enriched: encoding document..."))
b2f51b24
BG
285 (save-restriction
286 (narrow-to-region from to)
287 (delete-to-left-margin)
288 (unjustify-region)
289 (goto-char from)
290 (format-replace-strings '(("<" . "<<")))
2e4c0487 291 (format-insert-annotations
b2f51b24
BG
292 (format-annotate-region from (point-max) enriched-translations
293 'enriched-make-annotation enriched-ignore))
294 (goto-char from)
295 (insert (if (stringp enriched-initial-annotation)
296 enriched-initial-annotation
8b914003
RS
297 (save-excursion
298 ;; Eval this in the buffer we are annotating. This
299 ;; fixes a bug which was saving incorrect File-Width
300 ;; information, since we were looking at local
301 ;; variables in the wrong buffer.
302 (if orig-buf (set-buffer orig-buf))
303 (funcall enriched-initial-annotation))))
b2f51b24
BG
304 (enriched-map-property-regions 'hard
305 (lambda (v b e)
306 (if (and v (= ?\n (char-after b)))
307 (progn (goto-char b) (insert "\n"))))
308 (point) nil)
309 (if enriched-verbose (message nil))
310 ;; Return new end.
311 (point-max)))
0122281a 312
b8a8267b
RS
313(defun enriched-make-annotation (internal-ann positive)
314 "Format an annotation INTERNAL-ANN.
315INTERNAL-ANN may be a string, for a flag, or a list of the form (PARAM VALUE).
316If POSITIVE is non-nil, this is the opening annotation;
317if nil, the matching close."
687a9f30
PJ
318 (cond ((stringp internal-ann)
319 (format enriched-annotation-format (if positive "" "/") internal-ann))
b2f51b24
BG
320 ;; Otherwise it is an annotation with parameters, represented as a list
321 (positive
687a9f30
PJ
322 (let ((item (car internal-ann))
323 (params (cdr internal-ann)))
b2f51b24
BG
324 (concat (format enriched-annotation-format "" item)
325 (mapconcat (lambda (i) (concat "<param>" i "</param>"))
326 params ""))))
687a9f30 327 (t (format enriched-annotation-format "/" (car internal-ann)))))
0122281a 328
b2f51b24
BG
329(defun enriched-encode-other-face (old new)
330 "Generate annotations for random face change.
331One annotation each for foreground color, background color, italic, etc."
332 (cons (and old (enriched-face-ans old))
333 (and new (enriched-face-ans new))))
2e4c0487 334
b2f51b24 335(defun enriched-face-ans (face)
2dc96f0f
RS
336 "Return annotations specifying FACE.
337FACE may be a list of faces instead of a single face;
338it can also be anything allowed as an element of a list
339which can be the value of the `face' text property."
ed58ed83 340 (cond ((and (consp face) (eq (car face) 'foreground-color))
5dcfb3f4 341 (list (list "x-color" (cdr face))))
ed58ed83 342 ((and (consp face) (eq (car face) 'background-color))
5dcfb3f4 343 (list (list "x-bg-color" (cdr face))))
1896206d
EZ
344 ((and (listp face) (eq (car face) :foreground))
345 (list (list "x-color" (cadr face))))
346 ((and (listp face) (eq (car face) :background))
347 (list (list "x-bg-color" (cadr face))))
2dc96f0f
RS
348 ((listp face)
349 (apply 'append (mapcar 'enriched-face-ans face)))
c7957947
GM
350 ((let* ((fg (face-attribute face :foreground))
351 (bg (face-attribute face :background))
b2f51b24
BG
352 (props (face-font face t))
353 (ans (cdr (format-annotate-single-property-change
354 'face nil props enriched-translations))))
5dcfb3f4
GM
355 (unless (eq fg 'unspecified)
356 (setq ans (cons (list "x-color" fg) ans)))
357 (unless (eq bg 'unspecified)
358 (setq ans (cons (list "x-bg-color" bg) ans)))
b2f51b24 359 ans))))
0122281a
BG
360
361;;;
b2f51b24 362;;; Decoding files
0122281a
BG
363;;;
364
b2f51b24
BG
365;;;###autoload
366(defun enriched-decode (from to)
0122281a 367 (if enriched-verbose (message "Enriched: decoding document..."))
8b914003 368 (use-hard-newlines 1 'never)
0122281a
BG
369 (save-excursion
370 (save-restriction
b2f51b24
BG
371 (narrow-to-region from to)
372 (goto-char from)
8b914003
RS
373
374 ;; Deal with header
375 (let ((file-width (enriched-get-file-width)))
b2f51b24
BG
376 (enriched-remove-header)
377
378 ;; Deal with newlines
b2f51b24
BG
379 (while (search-forward-regexp "\n\n+" nil t)
380 (if (current-justification)
381 (delete-char -1))
8b914003 382 (set-hard-newline-properties (match-beginning 0) (point)))
b2f51b24
BG
383
384 ;; Translate annotations
385 (format-deannotate-region from (point-max) enriched-translations
386 'enriched-next-annotation)
387
8b914003
RS
388 ;; Indent or fill the buffer
389 (cond (file-width ; File was filled to this width
390 (setq fill-column file-width)
391 (if enriched-verbose (message "Indenting..."))
392 (enriched-insert-indentation))
393 (t ; File was not filled.
394 (if enriched-verbose (message "Filling paragraphs..."))
395 (fill-region (point-min) (point-max))))
396 (if enriched-verbose (message nil)))
b2f51b24
BG
397 (point-max))))
398
399(defun enriched-next-annotation ()
400 "Find and return next text/enriched annotation.
09291989 401Any \"<<\" strings encountered are converted to \"<\".
b2f51b24
BG
402Return value is \(begin end name positive-p), or nil if none was found."
403 (while (and (search-forward "<" nil 1)
404 (progn (goto-char (match-beginning 0))
405 (not (looking-at enriched-annotation-regexp))))
406 (forward-char 1)
407 (if (= ?< (char-after (point)))
408 (delete-char 1)
409 ;; A single < that does not start an annotation is an error,
410 ;; which we note and then ignore.
2e4c0487 411 (message "Warning: malformed annotation in file at %s"
a59d9084 412 (1- (point)))))
b2f51b24
BG
413 (if (not (eobp))
414 (let* ((beg (match-beginning 0))
415 (end (match-end 0))
2e4c0487 416 (name (downcase (buffer-substring
b2f51b24
BG
417 (match-beginning 2) (match-end 2))))
418 (pos (not (match-beginning 1))))
419 (list beg end name pos))))
0122281a
BG
420
421(defun enriched-get-file-width ()
422 "Look for file width information on this line."
423 (save-excursion
b2f51b24 424 (if (search-forward "Text-Width: " (+ (point) 1000) t)
0122281a
BG
425 (read (current-buffer)))))
426
b2f51b24
BG
427(defun enriched-remove-header ()
428 "Remove file-format header at point."
429 (while (looking-at "^[-A-Za-z]+: .*\n")
430 (delete-region (point) (match-end 0)))
431 (if (looking-at "^\n")
432 (delete-char 1)))
433
7ddb5524 434(defun enriched-decode-foreground (from to &optional color)
2a9cfe6a
RS
435 (if color
436 (list from to 'face (list ':foreground color))
437 (message "Warning: no color specified for <x-color>")
ae8f9257 438 nil))
b2f51b24 439
7ddb5524 440(defun enriched-decode-background (from to &optional color)
2a9cfe6a
RS
441 (if color
442 (list from to 'face (list ':background color))
443 (message "Warning: no color specified for <x-bg-color>")
ae8f9257 444 nil))
d9e28c1c
GM
445\f
446;;; Handling the `display' property.
447
448
449(defun enriched-handle-display-prop (old new)
450 "Return a list of annotations for a change in the `display' property.
451OLD is the old value of the property, NEW is the new value. Value
452is a list `(CLOSE OPEN)', where CLOSE is a list of annotations to
453close and OPEN a list of annotations to open. Each of these lists
454has the form `(ANNOTATION PARAM ...)'."
455 (let ((annotation "x-display")
97f51df1 456 (param (prin1-to-string (or old new))))
d9e28c1c 457 (if (null old)
687a9f30
PJ
458 (cons nil (list (list annotation param)))
459 (cons (list (list annotation param)) nil))))
d9e28c1c
GM
460
461(defun enriched-decode-display-prop (start end &optional param)
462 "Decode a `display' property for text between START and END.
463PARAM is a `<param>' found for the property.
464Value is a list `(START END SYMBOL VALUE)' with START and END denoting
465the range of text to assign text property SYMBOL with value VALUE "
466 (let ((prop (when (stringp param)
467 (condition-case ()
468 (car (read-from-string param))
469 (error nil)))))
470 (unless prop
471 (message "Warning: invalid <x-display> parameter %s" param))
472 (list start end 'display prop)))
2e4c0487 473
0122281a 474;;; enriched.el ends here