Mercurial > hg > xemacs-beta
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 |