comparison lisp/modes/enriched.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children ac2d302a0011
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 ;;; enriched.el --- read and save files in text/enriched format
2 ;; Copyright (c) 1994, 1995 Free Software Foundation, Inc.
3
4 ;; XEmacs version: Mike Sperber <sperber@informatik.uni-tuebingen.de>
5 ;; Original author: Boris Goldowsky <boris@gnu.ai.mit.edu>
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.
14 ;;
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.
19 ;;
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
22 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
23
24 ;;; Synched up with: FSF 19.30.
25
26 ;;; Commentary:
27 ;;
28 ;; This file implements reading, editing, and saving files with
29 ;; text-properties such as faces, levels of indentation, and true line breaks
30 ;; distinguished from newlines just used to fit text into the window.
31 ;;
32 ;; The file format used is the MIME text/enriched format, which is a
33 ;; standard format defined in internet RFC 1563. All standard
34 ;; annotations are supported.
35 ;;
36 ;; A separate file, enriched.doc, contains further documentation and other
37 ;; important information about this code. It also serves as an example file
38 ;; in text/enriched format. It should be in the etc directory of your emacs
39 ;; distribution.
40 ;;
41 ;;; TODO for the XEmacs port:
42 ;;
43 ;; Currently XEmacs does not support default-text-properties. The
44 ;; original enriched.el uses this to set the left-margin,
45 ;; right-margin, and justification properties to 'front-sticky.
46 ;; If you know the Right Way to fix this, contact
47 ;; Mike Sperber <sperber@informatik.uni-tuebingen.de>.
48
49 (provide 'enriched)
50 (require 'facemenu)
51
52 ;;;
53 ;;; Variables controlling the display
54 ;;;
55
56 (defvar enriched-verbose t
57 "*If non-nil, give status messages when reading and writing files.")
58
59 (defvar enriched-default-right-margin 10
60 "*Default amount of space to leave on the right edge of the screen.
61 This can be increased inside text by changing the 'right-margin text property.
62 Measured in character widths. If the screen is narrower than this, it is
63 assumed to be 0.")
64
65 (defvar enriched-fill-after-visiting t
66 "If t, fills paragraphs when reading in enriched documents.
67 If nil, only fills when you explicitly request it. If the value is 'ask, then
68 it will query you whether to fill.
69 Filling is never done if the current text-width is the same as the value
70 stored in the file.")
71
72 ;;;
73 ;;; Set up faces & display table
74 ;;;
75
76 (if (not (find-face 'fixed))
77 (copy-face 'default 'fixed))
78
79 (if (not (find-face 'excerpt))
80 (copy-face 'italic 'excerpt))
81
82 (defconst enriched-display-table (make-display-table))
83 (aset enriched-display-table ?\f (make-string (1- (frame-width)) ?-))
84
85 ;;;
86 ;;; Variables controlling the file format
87 ;;; (bidirectional)
88
89 (defconst enriched-initial-annotation
90 (lambda ()
91 (format "Content-Type: text/enriched\nText-Width: %d\n\n"
92 (enriched-text-width)))
93 "What to insert at the start of a text/enriched file.
94 If this is a string, it is inserted. If it is a list, it should be a lambda
95 expression, which is evaluated to get the string to insert.")
96
97 (defconst enriched-annotation-format "<%s%s>"
98 "General format of enriched-text annotations.")
99
100 (defconst enriched-annotation-regexp "<\\(/\\)?\\([-A-za-z0-9]+\\)>"
101 "Regular expression matching enriched-text annotations.")
102
103 (defconst enriched-translations
104 '((face (bold-italic "bold" "italic")
105 (bold "bold")
106 (italic "italic")
107 (underline "underline")
108 (fixed "fixed")
109 (excerpt "excerpt")
110 (default )
111 (nil enriched-encode-other-face))
112 (size (nil enriched-encode-size))
113 (justification (none "nofill")
114 (right "flushright")
115 (left "flushleft")
116 (full "flushboth")
117 (center "center"))
118 (left-margin (4 "indent"))
119 (right-margin (4 "indentright"))
120 (PARAMETER (t "param")) ; Argument of preceding annotation
121 (FUNCTION (enriched-decode-foreground "x-color")
122 (enriched-decode-background "x-bg-color")
123 (facemenu-make-larger "bigger")
124 (facemenu-make-smaller "smaller"))
125 (read-only (t "x-read-only"))
126 (unknown (nil format-annotate-value)))
127 "List of definitions of text/enriched annotations.
128 See `format-annotate-region' and `format-deannotate-region' for the definition
129 of this structure.")
130
131 (defconst enriched-ignore '(hard)
132 "Properties that are OK to ignore when saving text/enriched files.
133 Any property that is neither on this list nor dealt with by
134 `enriched-translations' will generate a warning.")
135
136 ;;; Internal variables
137
138 (defvar enriched-mode nil
139 "True if `enriched-mode' is in use.")
140 (make-variable-buffer-local 'enriched-mode)
141
142 (if (not (assq 'enriched-mode minor-mode-alist))
143 (setq minor-mode-alist
144 (cons '(enriched-mode " Enriched")
145 minor-mode-alist)))
146
147 (defvar enriched-mode-hooks nil
148 "Functions to run when entering `enriched-mode'.
149 If you set variables in this hook, you should arrange for them to be restored
150 to their old values if enriched-mode is left. One way to do this is to add
151 them and their old values to `enriched-old-bindings'.")
152
153 (defvar enriched-old-bindings nil
154 "Store old variable values that we change when entering mode.
155 The value is a list of \(VAR VALUE VAR VALUE...).")
156 (make-variable-buffer-local 'enriched-old-bindings)
157
158 (defvar enriched-text-width nil)
159 (make-variable-buffer-local 'enriched-text-width)
160
161 ;;;
162 ;;; Define the mode
163 ;;;
164
165 ;;;###autoload
166 (defun enriched-mode (&optional arg)
167 "Minor mode for editing text/enriched files.
168 These are files with embedded formatting information in the MIME standard
169 text/enriched format.
170 Turning the mode on runs `enriched-mode-hooks'.
171
172 More information about enriched-mode is available in the file
173 etc/enriched.doc in the Emacs distribution directory.
174
175 Commands:
176
177 \\<enriched-mode-map>\\{enriched-mode-map}"
178 (interactive "P")
179 (let ((mod (buffer-modified-p)))
180 (cond ((or (<= (prefix-numeric-value arg) 0)
181 (and enriched-mode (null arg)))
182 ;; Turn mode off
183 (setq enriched-mode nil)
184 (setq buffer-file-format (delq 'text/enriched buffer-file-format))
185 ;; restore old variable values
186 (while enriched-old-bindings
187 (funcall 'set (car enriched-old-bindings)
188 (car (cdr enriched-old-bindings)))
189 (setq enriched-old-bindings (cdr (cdr enriched-old-bindings)))))
190
191 (enriched-mode nil) ; Mode already on; do nothing.
192
193 (t (setq enriched-mode t) ; Turn mode on
194 (if (not (memq 'text/enriched buffer-file-format))
195 (setq buffer-file-format
196 (cons 'text/enriched buffer-file-format)))
197 ;; Save old variable values before we change them.
198 ;; These will be restored if we exit enriched-mode.
199 (setq enriched-old-bindings
200 (list 'indent-line-function indent-line-function
201 'use-hard-newlines use-hard-newlines))
202 (make-local-variable 'indent-line-function)
203 (make-local-variable 'use-hard-newlines)
204 (setq indent-line-function 'indent-to-left-margin
205 use-hard-newlines t)
206
207 ;; copy display table
208 (frob-display-table
209 #'(lambda (dt)
210 (let ((l (length enriched-display-table))
211 (c 0))
212 (while (< c l)
213 (let ((v (aref enriched-display-table c)))
214 (if v
215 (aset dt c v)))
216 (setq c (1+ c)))))
217 (current-buffer))
218 (run-hooks 'enriched-mode-hooks)))
219 (set-buffer-modified-p mod)
220 (redraw-modeline)))
221
222 ;;;
223 ;;; Keybindings
224 ;;;
225
226 (defvar enriched-mode-map nil
227 "Keymap for `enriched-mode'.")
228
229 (if (null enriched-mode-map)
230 (fset 'enriched-mode-map (setq enriched-mode-map (make-sparse-keymap))))
231
232 (if (not (assq 'enriched-mode minor-mode-map-alist))
233 (setq minor-mode-map-alist
234 (cons (cons 'enriched-mode enriched-mode-map)
235 minor-mode-map-alist)))
236
237 (define-key enriched-mode-map "\C-a" 'beginning-of-line-text)
238 (define-key enriched-mode-map "\C-m" 'reindent-then-newline-and-indent)
239 (define-key enriched-mode-map "\C-j" 'reindent-then-newline-and-indent)
240 (define-key enriched-mode-map "\M-j" 'facemenu-justification-menu)
241 (define-key enriched-mode-map "\M-S" 'set-justification-center)
242 (define-key enriched-mode-map "\C-x\t" 'increase-left-margin)
243 (define-key enriched-mode-map "\C-c\C-l" 'set-left-margin)
244 (define-key enriched-mode-map "\C-c\C-r" 'set-right-margin)
245
246 ;;;
247 ;;; Some functions dealing with text-properties, especially indentation
248 ;;;
249
250 (defun enriched-map-property-regions (prop func &optional from to)
251 "Apply a function to regions of the buffer based on a text property.
252 For each contiguous region of the buffer for which the value of PROPERTY is
253 eq, the FUNCTION will be called. Optional arguments FROM and TO specify the
254 region over which to scan.
255
256 The specified function receives three arguments: the VALUE of the property in
257 the region, and the START and END of each region."
258 (save-excursion
259 (save-restriction
260 (if to (narrow-to-region (point-min) to))
261 (goto-char (or from (point-min)))
262 (let ((begin (point))
263 end
264 (marker (make-marker))
265 (val (get-text-property (point) prop)))
266 (while (setq end (text-property-not-all begin (point-max) prop val))
267 (move-marker marker end)
268 (funcall func val begin (marker-position marker))
269 (setq begin (marker-position marker)
270 val (get-text-property marker prop)))
271 (if (< begin (point-max))
272 (funcall func val begin (point-max)))))))
273
274 (put 'enriched-map-property-regions 'lisp-indent-hook 1)
275
276 (defun enriched-insert-indentation (&optional from to)
277 "Indent and justify each line in the region."
278 (save-excursion
279 (save-restriction
280 (if to (narrow-to-region (point-min) to))
281 (goto-char (or from (point-min)))
282 (if (not (bolp)) (forward-line 1))
283 (while (not (eobp))
284 (if (eolp)
285 nil ; skip blank lines
286 (indent-to (current-left-margin))
287 (justify-current-line t nil t))
288 (forward-line 1)))))
289
290 (defun enriched-text-width ()
291 "The width of unindented text in this window, in characters.
292 This is the width of the window minus `enriched-default-right-margin'."
293 (or enriched-text-width
294 (let ((ww (window-width)))
295 (setq enriched-text-width
296 (if (> ww enriched-default-right-margin)
297 (- ww enriched-default-right-margin)
298 ww)))))
299
300 ;;;
301 ;;; Encoding Files
302 ;;;
303
304 ;;;###autoload
305 (defun enriched-encode (from to)
306 (if enriched-verbose (message "Enriched: encoding document..."))
307 (save-restriction
308 (narrow-to-region from to)
309 (delete-to-left-margin)
310 (unjustify-region)
311 (goto-char from)
312 (format-replace-strings '(("<" . "<<")))
313 (format-insert-annotations
314 (format-annotate-region from (point-max) enriched-translations
315 'enriched-make-annotation enriched-ignore))
316 (goto-char from)
317 (insert (if (stringp enriched-initial-annotation)
318 enriched-initial-annotation
319 (funcall enriched-initial-annotation)))
320 (enriched-map-property-regions 'hard
321 (lambda (v b e)
322 (if (and v (= ?\n (char-after b)))
323 (progn (goto-char b) (insert "\n"))))
324 (point) nil)
325 (if enriched-verbose (message nil))
326 ;; Return new end.
327 (point-max)))
328
329 (defun enriched-make-annotation (name positive)
330 "Format an annotation called NAME.
331 If POSITIVE is non-nil, this is the opening annotation, if nil, this is the
332 matching close."
333 (cond ((stringp name)
334 (format enriched-annotation-format (if positive "" "/") name))
335 ;; Otherwise it is an annotation with parameters, represented as a list
336 (positive
337 (let ((item (car name))
338 (params (cdr name)))
339 (concat (format enriched-annotation-format "" item)
340 (mapconcat (lambda (i) (concat "<param>" i "</param>"))
341 params ""))))
342 (t (format enriched-annotation-format "/" (car name)))))
343
344 (defun enriched-face-strip-size (face)
345 "Create a symbol from the name of FACE devoid of size information,
346 i.e. remove all larger- and smaller- prefixes."
347 (let* ((face-symbol (face-name face))
348 (face-name (symbol-name face-symbol))
349 (old-name face-name)
350 new-name)
351 (while
352 (not (string-equal
353 old-name
354 (setq new-name (replace-in-string old-name "^larger-" ""))))
355 (setq old-name new-name))
356
357 (while
358 (not (string-equal
359 old-name
360 (setq new-name (replace-in-string old-name "^smaller-" ""))))
361 (setq old-name new-name))
362
363 (if (string-equal new-name face-name)
364 face-symbol
365 (intern new-name))))
366
367 (defun enriched-encode-other-face (old new)
368 "Generate annotations for random face change.
369 One annotation each for foreground color, background color, italic, etc."
370 (cons (and old (enriched-face-ans old))
371 (and new (enriched-face-ans new))))
372
373 (defun enriched-face-ans (face)
374 "Return annotations specifying FACE."
375 (let ((face-name (symbol-name face)))
376 (cond ((string-match "^fg:" face-name)
377 (list (list "x-color" (substring face-name 3))))
378 ((string-match "^bg:" face-name)
379 (list (list "x-bg-color" (substring face-name 3))))
380 ((or (string-match "^larger-" face-name)
381 (string-match "^smaller-" face-name))
382 (cdr (format-annotate-single-property-change
383 'face nil (enriched-face-strip-size face)
384 enriched-translations)))
385 (t
386 (let* ((fg (and (not (eq (face-foreground face)
387 (face-foreground 'default)))
388 (color-instance-name (face-foreground face))))
389 (bg (and (not (eq (face-background face)
390 (face-background 'default)))
391 (color-instance-name (face-background face))))
392 (ans '()))
393 (if fg (setq ans (cons (list "x-color" fg) ans)))
394 (if bg (setq ans (cons (list "x-bg-color" bg) ans)))
395 ans)))))
396
397
398 (defun enriched-size-annotation (n annotation)
399 "Generate ANNOTATION N times."
400 (let ((l '()))
401 (while (not (zerop n))
402 (setq l (cons annotation l))
403 (setq n (1- n)))
404 l))
405
406 (defun enriched-encode-size (old new)
407 "Return annotations specifying SIZE."
408 (let* ((old (or old 0))
409 (new (or new 0))
410 (closing-annotation
411 (enriched-size-annotation (abs old)
412 (if (> old 0) "bigger" "smaller")))
413 (opening-annotation
414 (enriched-size-annotation (abs new)
415 (if (> new 0) "bigger" "smaller"))))
416 (cons closing-annotation
417 opening-annotation)))
418
419 ;;;
420 ;;; Decoding files
421 ;;;
422
423 ;;;###autoload
424 (defun enriched-decode (from to)
425 (if enriched-verbose (message "Enriched: decoding document..."))
426 (save-excursion
427 (save-restriction
428 (narrow-to-region from to)
429 (goto-char from)
430 (let ((file-width (enriched-get-file-width))
431 (use-hard-newlines t))
432 (enriched-remove-header)
433
434 ;; Deal with newlines
435 (goto-char from)
436 (while (search-forward-regexp "\n\n+" nil t)
437 (if (current-justification)
438 (delete-char -1))
439 (put-text-property (match-beginning 0) (point) 'hard t)
440 (put-text-property (match-beginning 0) (point) 'front-sticky nil))
441
442 ;; Translate annotations
443 (format-deannotate-region from (point-max) enriched-translations
444 'enriched-next-annotation)
445
446 ;; Fill paragraphs
447 (if (or (and file-width ; possible reasons not to fill:
448 (= file-width (enriched-text-width))) ; correct wd.
449 (null enriched-fill-after-visiting) ; never fill
450 (and (eq 'ask enriched-fill-after-visiting) ; asked & declined
451 (not (y-or-n-p "Re-fill for current display width? "))))
452 ;; Minimally, we have to insert indentation and justification.
453 (enriched-insert-indentation)
454 (if enriched-verbose (message "Filling paragraphs..."))
455 (fill-region (point-min) (point-max))))
456 (if enriched-verbose (message nil))
457 (point-max))))
458
459 (defun enriched-next-annotation ()
460 "Find and return next text/enriched annotation.
461 Any \"<<\" strings encountered are converted to \"<\".
462 Return value is \(begin end name positive-p), or nil if none was found."
463 (while (and (search-forward "<" nil 1)
464 (progn (goto-char (match-beginning 0))
465 (not (looking-at enriched-annotation-regexp))))
466 (forward-char 1)
467 (if (= ?< (char-after (point)))
468 (delete-char 1)
469 ;; A single < that does not start an annotation is an error,
470 ;; which we note and then ignore.
471 (message (format "Warning: malformed annotation in file at %s"
472 (1- (point))))))
473 (if (not (eobp))
474 (let* ((beg (match-beginning 0))
475 (end (match-end 0))
476 (name (downcase (buffer-substring
477 (match-beginning 2) (match-end 2))))
478 (pos (not (match-beginning 1))))
479 (list beg end name pos))))
480
481 (defun enriched-get-file-width ()
482 "Look for file width information on this line."
483 (save-excursion
484 (if (search-forward "Text-Width: " (+ (point) 1000) t)
485 (read (current-buffer)))))
486
487 (defun enriched-remove-header ()
488 "Remove file-format header at point."
489 (while (looking-at "^[-A-Za-z]+: .*\n")
490 (delete-region (point) (match-end 0)))
491 (if (looking-at "^\n")
492 (delete-char 1)))
493
494 (defun enriched-decode-foreground (from to color)
495 (let ((face (facemenu-get-face (intern (concat "fg:" color)))))
496 (if (not face)
497 (progn
498 (make-face face)
499 (message "Warning: Color \"%s\" can't be displayed." color)))
500 (list from to 'face face)))
501
502 (defun enriched-decode-background (from to color)
503 (let ((face (facemenu-get-face (intern (concat "bg:" color)))))
504 (if (not face)
505 (progn
506 (make-face face)
507 (message "Warning: Color \"%s\" can't be displayed." color)))
508 (list from to 'face face)))
509
510 ;;; enriched.el ends here