(completion-kill-region): Doc fix.
[bpt/emacs.git] / lisp / format.el
CommitLineData
a8581027 1;;; format.el --- read and save files in multiple formats
b578f267 2
bb3bd74c 3;; Copyright (c) 1994, 1995, 1997, 1999, 2004 Free Software Foundation
029894b9 4
823139fb 5;; Author: Boris Goldowsky <boris@gnu.org>
029894b9
BG
6
7;; This file is part of GNU Emacs.
8
9;; GNU Emacs is free software; you can redistribute it and/or modify
10;; it under the terms of the GNU General Public License as published by
11;; the Free Software Foundation; either version 2, or (at your option)
12;; any later version.
b578f267 13
029894b9
BG
14;; GNU Emacs is distributed in the hope that it will be useful,
15;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17;; GNU General Public License for more details.
b578f267 18
029894b9 19;; You should have received a copy of the GNU General Public License
b578f267
EN
20;; along with GNU Emacs; see the file COPYING. If not, write to the
21;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22;; Boston, MA 02111-1307, USA.
029894b9
BG
23
24;;; Commentary:
b578f267
EN
25
26;; This file defines a unified mechanism for saving & loading files stored
27;; in different formats. `format-alist' contains information that directs
029894b9 28;; Emacs to call an encoding or decoding function when reading or writing
823139fb 29;; files that match certain conditions.
029894b9 30;;
b578f267
EN
31;; When a file is visited, its format is determined by matching the
32;; beginning of the file against regular expressions stored in
33;; `format-alist'. If this fails, you can manually translate the buffer
34;; using `format-decode-buffer'. In either case, the formats used are
35;; listed in the variable `buffer-file-format', and become the default
36;; format for saving the buffer. To save a buffer in a different format,
37;; change this variable, or use `format-write-file'.
029894b9
BG
38;;
39;; Auto-save files are normally created in the same format as the visited
b578f267
EN
40;; file, but the variable `auto-save-file-format' can be set to a
41;; particularly fast or otherwise preferred format to be used for
42;; auto-saving (or nil to do no encoding on auto-save files, but then you
43;; risk losing any text-properties in the buffer).
029894b9 44;;
b578f267
EN
45;; You can manually translate a buffer into or out of a particular format
46;; with the functions `format-encode-buffer' and `format-decode-buffer'.
47;; To translate just the region use the functions `format-encode-region'
823139fb 48;; and `format-decode-region'.
029894b9 49;;
b578f267
EN
50;; You can define a new format by writing the encoding and decoding
51;; functions, and adding an entry to `format-alist'. See enriched.el for
52;; an example of how to implement a file format. There are various
53;; functions defined in this file that may be useful for writing the
54;; encoding and decoding functions:
55;; * `format-annotate-region' and `format-deannotate-region' allow a
56;; single alist of information to be used for encoding and decoding.
57;; The alist defines a correspondence between strings in the file
58;; ("annotations") and text-properties in the buffer.
029894b9
BG
59;; * `format-replace-strings' is similarly useful for doing simple
60;; string->string translations in a reversible manner.
61
b578f267
EN
62;;; Code:
63
029894b9
BG
64(put 'buffer-file-format 'permanent-local t)
65
823139fb 66(defvar format-alist
8d0dd9d2 67 '((text/enriched "Extended MIME text/enriched format."
029894b9
BG
68 "Content-[Tt]ype:[ \t]*text/enriched"
69 enriched-decode enriched-encode t enriched-mode)
c84cf2bf 70 (plain "ISO 8859-1 standard format, no text properties."
029894b9
BG
71 ;; Plain only exists so that there is an obvious neutral choice in
72 ;; the completion list.
c84cf2bf 73 nil nil nil nil nil)
823139fb
DL
74 (ibm "IBM Code Page 850 (DOS)"
75 nil ; The original "1\\(^\\)" is obscure.
271af233 76 "recode -f ibm-pc:latin1" "recode -f latin1:ibm-pc" t nil)
823139fb
DL
77 (mac "Apple Macintosh"
78 nil
271af233 79 "recode -f mac:latin1" "recode -f latin1:mac" t nil)
823139fb
DL
80 (hp "HP Roman8"
81 nil
271af233 82 "recode -f roman8:latin1" "recode -f latin1:roman8" t nil)
823139fb
DL
83 (TeX "TeX (encoding)"
84 nil
c84cf2bf 85 iso-tex2iso iso-iso2tex t nil)
823139fb
DL
86 (gtex "German TeX (encoding)"
87 nil
c84cf2bf 88 iso-gtex2iso iso-iso2gtex t nil)
f1d6fe69 89 (html "HTML/SGML \"ISO 8879:1986//ENTITIES Added Latin 1//EN\" (encoding)"
823139fb 90 nil
f1d6fe69 91 iso-sgml2iso iso-iso2sgml t nil)
823139fb
DL
92 (rot13 "rot13"
93 nil
c84cf2bf 94 "tr a-mn-z n-za-m" "tr a-mn-z n-za-m" t nil)
823139fb
DL
95 (duden "Duden Ersatzdarstellung"
96 nil
97 "diac" iso-iso2duden t nil)
98 (de646 "German ASCII (ISO 646)"
99 nil
271af233 100 "recode -f iso646-ge:latin1" "recode -f latin1:iso646-ge" t nil)
823139fb
DL
101 (denet "net German"
102 nil
c84cf2bf 103 iso-german iso-cvt-read-only t nil)
823139fb
DL
104 (esnet "net Spanish"
105 nil
8d0dd9d2 106 iso-spanish iso-cvt-read-only t nil))
029894b9
BG
107 "List of information about understood file formats.
108Elements are of the form \(NAME DOC-STR REGEXP FROM-FN TO-FN MODIFY MODE-FN).
d1ae737b 109
029894b9 110NAME is a symbol, which is stored in `buffer-file-format'.
d1ae737b 111
029894b9
BG
112DOC-STR should be a single line providing more information about the
113 format. It is currently unused, but in the future will be shown to
114 the user if they ask for more information.
d1ae737b 115
029894b9 116REGEXP is a regular expression to match against the beginning of the file;
823139fb
DL
117 it should match only files in that format. Use nil to avoid
118 matching at all for formats for which this isn't appropriate to
119 require explicit encoding/decoding.
d1ae737b 120
823139fb 121FROM-FN is called to decode files in that format; it gets two args, BEGIN
029894b9
BG
122 and END, and can make any modifications it likes, returning the new
123 end. It must make sure that the beginning of the file no longer
124 matches REGEXP, or else it will get called again.
d1ae737b
RS
125 Alternatively, FROM-FN can be a string, which specifies a shell command
126 (including options) to be used as a filter to perform the conversion.
127
d1abb42f
RS
128TO-FN is called to encode a region into that format; it is passed three
129 arguments: BEGIN, END, and BUFFER. BUFFER is the original buffer that
130 the data being written came from, which the function could use, for
131 example, to find the values of local variables. TO-FN should either
132 return a list of annotations like `write-region-annotate-functions',
133 or modify the region and return the new end.
d1ae737b
RS
134 Alternatively, TO-FN can be a string, which specifies a shell command
135 (including options) to be used as a filter to perform the conversion.
136
d1abb42f
RS
137MODIFY, if non-nil, means the TO-FN wants to modify the region. If nil,
138 TO-FN will not make any changes but will instead return a list of
823139fb 139 annotations.
d1ae737b 140
3e224645
EZ
141MODE-FN, if specified, is called when visiting a file with that format.
142 It is called with a single positive argument, on the assumption
34f30f79
RS
143 that it turns on some Emacs mode.
144
145PRESERVE, if non-nil, means that `format-write-file' should not remove
146 this format from `buffer-file-formats'.")
029894b9
BG
147
148;;; Basic Functions (called from Lisp)
149
c84cf2bf
RS
150(defun format-encode-run-method (method from to &optional buffer)
151 "Translate using function or shell script METHOD the text from FROM to TO.
152If METHOD is a string, it is a shell command;
153otherwise, it should be a Lisp function.
154BUFFER should be the buffer that the output originally came from."
155 (if (stringp method)
823139fb 156 (let ((error-buff (get-buffer-create "*Format Errors*"))
7cc45a35 157 (coding-system-for-read 'no-conversion)
823139fb
DL
158 format-alist)
159 (with-current-buffer error-buff
160 (widen)
161 (erase-buffer))
10fa0073
GM
162 (if (and (zerop (save-window-excursion
163 (shell-command-on-region from to method t t
164 error-buff)))
823139fb
DL
165 ;; gzip gives zero exit status with bad args, for instance.
166 (zerop (with-current-buffer error-buff
167 (buffer-size))))
168 (bury-buffer error-buff)
169 (switch-to-buffer-other-window error-buff)
10fa0073 170 (error "Format encoding failed")))
c84cf2bf
RS
171 (funcall method from to buffer)))
172
173(defun format-decode-run-method (method from to &optional buffer)
174 "Decode using function or shell script METHOD the text from FROM to TO.
823139fb
DL
175If METHOD is a string, it is a shell command; otherwise, it should be
176a Lisp function. Decoding is done for the given BUFFER."
c84cf2bf 177 (if (stringp method)
823139fb 178 (let ((error-buff (get-buffer-create "*Format Errors*"))
7cc45a35 179 (coding-system-for-write 'no-conversion)
823139fb
DL
180 format-alist)
181 (with-current-buffer error-buff
182 (widen)
183 (erase-buffer))
184 ;; We should perhaps go via a temporary buffer and copy it
185 ;; back, in case of errors.
186 (if (and (zerop (save-window-excursion
187 (shell-command-on-region (point-min) (point-max)
188 method t t
189 error-buff)))
190 ;; gzip gives zero exit status with bad args, for instance.
191 (zerop (with-current-buffer error-buff
192 (buffer-size))))
193 (bury-buffer error-buff)
194 (switch-to-buffer-other-window error-buff)
195 (error "Format decoding failed"))
c84cf2bf
RS
196 (point))
197 (funcall method from to)))
198
10fa0073 199(defun format-annotate-function (format from to orig-buf format-count)
823139fb 200 "Return annotations for writing region as FORMAT.
029894b9
BG
201FORMAT is a symbol naming one of the formats defined in `format-alist',
202it must be a single symbol, not a list like `buffer-file-format'.
d1abb42f
RS
203FROM and TO delimit the region to be operated on in the current buffer.
204ORIG-BUF is the original buffer that the data came from.
10fa0073
GM
205
206FORMAT-COUNT is an integer specifying how many times this function has
207been called in the process of decoding ORIG-BUF.
208
029894b9
BG
209This function works like a function on `write-region-annotate-functions':
210it either returns a list of annotations, or returns with a different buffer
10fa0073
GM
211current, which contains the modified text to write. In the latter case,
212this function's value is nil.
029894b9
BG
213
214For most purposes, consider using `format-encode-region' instead."
10fa0073
GM
215 ;; This function is called by write-region (actually
216 ;; build_annotations) for each element of buffer-file-format.
029894b9
BG
217 (let* ((info (assq format format-alist))
218 (to-fn (nth 4 info))
219 (modify (nth 5 info)))
220 (if to-fn
221 (if modify
222 ;; To-function wants to modify region. Copy to safe place.
10fa0073 223 (let ((copy-buf (get-buffer-create (format " *Format Temp %d*"
8a2f1176
RS
224 format-count)))
225 (sel-disp selective-display)
5258c763
JB
226 (multibyte enable-multibyte-characters)
227 (coding-system buffer-file-coding-system))
8a2f1176
RS
228 (with-current-buffer copy-buf
229 (setq selective-display sel-disp)
5258c763
JB
230 (set-buffer-multibyte multibyte)
231 (setq buffer-file-coding-system coding-system))
029894b9
BG
232 (copy-to-buffer copy-buf from to)
233 (set-buffer copy-buf)
234 (format-insert-annotations write-region-annotations-so-far from)
c84cf2bf 235 (format-encode-run-method to-fn (point-min) (point-max) orig-buf)
029894b9
BG
236 nil)
237 ;; Otherwise just call function, it will return annotations.
d1abb42f 238 (funcall to-fn from to orig-buf)))))
029894b9
BG
239
240(defun format-decode (format length &optional visit-flag)
241 ;; This function is called by insert-file-contents whenever a file is read.
242 "Decode text from any known FORMAT.
823139fb 243FORMAT is a symbol appearing in `format-alist' or a list of such symbols,
029894b9
BG
244or nil, in which case this function tries to guess the format of the data by
245matching against the regular expressions in `format-alist'. After a match is
246found and the region decoded, the alist is searched again from the beginning
247for another match.
248
249Second arg LENGTH is the number of characters following point to operate on.
250If optional third arg VISIT-FLAG is true, set `buffer-file-format'
be227f22
SM
251to the reverted list of formats used, and call any mode functions defined
252for those formats.
029894b9
BG
253
254Returns the new length of the decoded region.
255
256For most purposes, consider using `format-decode-region' instead."
257 (let ((mod (buffer-modified-p))
10fa0073 258 (begin (point))
029894b9 259 (end (+ (point) length)))
10fa0073
GM
260 (unwind-protect
261 (progn
262 ;; Don't record undo information for the decoding.
71296446 263
10fa0073
GM
264 (if (null format)
265 ;; Figure out which format it is in, remember list in `format'.
266 (let ((try format-alist))
267 (while try
268 (let* ((f (car try))
269 (regexp (nth 2 f))
270 (p (point)))
271 (if (and regexp (looking-at regexp)
272 (< (match-end 0) (+ begin length)))
273 (progn
be227f22 274 (push (car f) format)
10fa0073
GM
275 ;; Decode it
276 (if (nth 3 f)
277 (setq end (format-decode-run-method (nth 3 f) begin end)))
278 ;; Call visit function if required
279 (if (and visit-flag (nth 6 f)) (funcall (nth 6 f) 1))
280 ;; Safeguard against either of the functions changing pt.
281 (goto-char p)
282 ;; Rewind list to look for another format
283 (setq try format-alist))
284 (setq try (cdr try))))))
285 ;; Deal with given format(s)
286 (or (listp format) (setq format (list format)))
287 (let ((do format) f)
288 (while do
289 (or (setq f (assq (car do) format-alist))
bb3bd74c 290 (error "Unknown format %s" (car do)))
10fa0073
GM
291 ;; Decode:
292 (if (nth 3 f)
293 (setq end (format-decode-run-method (nth 3 f) begin end)))
294 ;; Call visit function if required
295 (if (and visit-flag (nth 6 f)) (funcall (nth 6 f) 1))
be227f22
SM
296 (setq do (cdr do))))
297 ;; Encode in the opposite order.
298 (setq format (reverse format)))
10fa0073
GM
299 (if visit-flag
300 (setq buffer-file-format format)))
71296446 301
021cfbea
GM
302 (set-buffer-modified-p mod))
303
10fa0073 304 ;; Return new length of region
029894b9
BG
305 (- end begin)))
306
307;;;
308;;; Interactive functions & entry points
309;;;
310
311(defun format-decode-buffer (&optional format)
312 "Translate the buffer from some FORMAT.
313If the format is not specified, this function attempts to guess.
823139fb 314`buffer-file-format' is set to the format used, and any mode-functions
029894b9
BG
315for the format are called."
316 (interactive
317 (list (format-read "Translate buffer from format (default: guess): ")))
318 (save-excursion
319 (goto-char (point-min))
320 (format-decode format (buffer-size) t)))
321
322(defun format-decode-region (from to &optional format)
323 "Decode the region from some format.
324Arg FORMAT is optional; if omitted the format will be determined by looking
325for identifying regular expressions at the beginning of the region."
326 (interactive
823139fb 327 (list (region-beginning) (region-end)
029894b9
BG
328 (format-read "Translate region from format (default: guess): ")))
329 (save-excursion
330 (goto-char from)
331 (format-decode format (- to from) nil)))
332
333(defun format-encode-buffer (&optional format)
334 "Translate the buffer into FORMAT.
335FORMAT defaults to `buffer-file-format'. It is a symbol naming one of the
336formats defined in `format-alist', or a list of such symbols."
337 (interactive
338 (list (format-read (format "Translate buffer to format (default %s): "
339 buffer-file-format))))
340 (format-encode-region (point-min) (point-max) format))
341
342(defun format-encode-region (beg end &optional format)
823139fb 343 "Translate the region into some FORMAT.
029894b9
BG
344FORMAT defaults to `buffer-file-format', it is a symbol naming
345one of the formats defined in `format-alist', or a list of such symbols."
823139fb
DL
346 (interactive
347 (list (region-beginning) (region-end)
348 (format-read (format "Translate region to format (default %s): "
349 buffer-file-format))))
350 (if (null format) (setq format buffer-file-format))
351 (if (symbolp format) (setq format (list format)))
352 (save-excursion
353 (goto-char end)
354 (let ((cur-buf (current-buffer))
355 (end (point-marker)))
356 (while format
357 (let* ((info (assq (car format) format-alist))
358 (to-fn (nth 4 info))
359 (modify (nth 5 info))
360 result)
361 (if to-fn
362 (if modify
363 (setq end (format-encode-run-method to-fn beg end
364 (current-buffer)))
365 (format-insert-annotations
366 (funcall to-fn beg end (current-buffer)))))
367 (setq format (cdr format)))))))
029894b9 368
8c5fe07f 369(defun format-write-file (filename format &optional confirm)
823139fb 370 "Write current buffer into file FILENAME using some FORMAT.
8c5fe07f 371Make buffer visit that file and set the format as the default for future
029894b9 372saves. If the buffer is already visiting a file, you can specify a directory
8c5fe07f
LT
373name as FILENAME, to write a file of the same old name in that directory.
374
375If optional third arg CONFIRM is non-nil, this function asks for
376confirmation before overwriting an existing file. Interactively,
377confirmation is required unless you supply a prefix argument."
029894b9
BG
378 (interactive
379 ;; Same interactive spec as write-file, plus format question.
380 (let* ((file (if buffer-file-name
381 (read-file-name "Write file: "
382 nil nil nil nil)
383 (read-file-name "Write file: "
384 (cdr (assq 'default-directory
385 (buffer-local-variables)))
386 nil nil (buffer-name))))
823139fb 387 (fmt (format-read (format "Write file `%s' in format: "
029894b9 388 (file-name-nondirectory file)))))
8c5fe07f 389 (list file fmt (not current-prefix-arg))))
34f30f79
RS
390 (let ((old-formats buffer-file-format)
391 preserve-formats)
392 (dolist (fmt old-formats)
393 (let ((aelt (assq fmt format-alist)))
394 (if (nth 7 aelt)
395 (push fmt preserve-formats))))
396 (setq buffer-file-format format)
397 (dolist (fmt preserve-formats)
398 (unless (memq fmt buffer-file-format)
399 (setq buffer-file-format (append buffer-file-format (list fmt))))))
8c5fe07f 400 (write-file filename confirm))
029894b9 401
ce6af52b 402(defun format-find-file (filename format)
823139fb 403 "Find the file FILENAME using data format FORMAT.
ce6af52b
KH
404If FORMAT is nil then do not do any format conversion."
405 (interactive
406 ;; Same interactive spec as write-file, plus format question.
407 (let* ((file (read-file-name "Find file: "))
823139fb 408 (fmt (format-read (format "Read file `%s' in format: "
ce6af52b
KH
409 (file-name-nondirectory file)))))
410 (list file fmt)))
411 (let ((format-alist nil))
412 (find-file filename))
413 (if format
414 (format-decode-buffer format)))
415
416(defun format-insert-file (filename format &optional beg end)
823139fb 417 "Insert the contents of file FILENAME using data format FORMAT.
ce6af52b
KH
418If FORMAT is nil then do not do any format conversion.
419The optional third and fourth arguments BEG and END specify
420the part of the file to read.
421
422The return value is like the value of `insert-file-contents':
19e580b4 423a list (ABSOLUTE-FILE-NAME SIZE)."
ce6af52b
KH
424 (interactive
425 ;; Same interactive spec as write-file, plus format question.
426 (let* ((file (read-file-name "Find file: "))
823139fb 427 (fmt (format-read (format "Read file `%s' in format: "
ce6af52b
KH
428 (file-name-nondirectory file)))))
429 (list file fmt)))
430 (let (value size)
431 (let ((format-alist nil))
432 (setq value (insert-file-contents filename nil beg end))
433 (setq size (nth 1 value)))
434 (if format
c84cf2bf 435 (setq size (format-decode format size)
19e580b4 436 value (list (car value) size)))
ce6af52b
KH
437 value))
438
029894b9
BG
439(defun format-read (&optional prompt)
440 "Read and return the name of a format.
441Return value is a list, like `buffer-file-format'; it may be nil.
442Formats are defined in `format-alist'. Optional arg is the PROMPT to use."
443 (let* ((table (mapcar (lambda (x) (list (symbol-name (car x))))
444 format-alist))
445 (ans (completing-read (or prompt "Format: ") table nil t)))
446 (if (not (equal "" ans)) (list (intern ans)))))
447
448
449;;;
450;;; Below are some functions that may be useful in writing encoding and
451;;; decoding functions for use in format-alist.
452;;;
453
454(defun format-replace-strings (alist &optional reverse beg end)
455 "Do multiple replacements on the buffer.
d0fc7e3b 456ALIST is a list of (FROM . TO) pairs, which should be proper arguments to
029894b9 457`search-forward' and `replace-match' respectively.
d0fc7e3b 458Optional 2nd arg REVERSE, if non-nil, means the pairs are (TO . FROM), so that
029894b9 459you can use the same list in both directions if it contains only literal
823139fb
DL
460strings.
461Optional args BEG and END specify a region of the buffer on which to operate."
029894b9
BG
462 (save-excursion
463 (save-restriction
464 (or beg (setq beg (point-min)))
465 (if end (narrow-to-region (point-min) end))
466 (while alist
467 (let ((from (if reverse (cdr (car alist)) (car (car alist))))
2c3ac81d 468 (to (if reverse (car (car alist)) (cdr (car alist)))))
029894b9
BG
469 (goto-char beg)
470 (while (search-forward from nil t)
471 (goto-char (match-beginning 0))
472 (insert to)
473 (set-text-properties (- (point) (length to)) (point)
474 (text-properties-at (point)))
475 (delete-region (point) (+ (point) (- (match-end 0)
476 (match-beginning 0)))))
477 (setq alist (cdr alist)))))))
478
479;;; Some list-manipulation functions that we need.
480
481(defun format-delq-cons (cons list)
823139fb
DL
482 "Remove the given CONS from LIST by side effect and return the new LIST.
483Since CONS could be the first element of LIST, write
484`\(setq foo \(format-delq-cons element foo))' to be sure of changing
485the value of `foo'."
029894b9
BG
486 (if (eq cons list)
487 (cdr list)
488 (let ((p list))
489 (while (not (eq (cdr p) cons))
e8af40ee 490 (if (null p) (error "format-delq-cons: not an element"))
029894b9
BG
491 (setq p (cdr p)))
492 ;; Now (cdr p) is the cons to delete
493 (setcdr p (cdr cons))
494 list)))
71296446 495
029894b9
BG
496(defun format-make-relatively-unique (a b)
497 "Delete common elements of lists A and B, return as pair.
498Compares using `equal'."
499 (let* ((acopy (copy-sequence a))
500 (bcopy (copy-sequence b))
501 (tail acopy))
502 (while tail
503 (let ((dup (member (car tail) bcopy))
504 (next (cdr tail)))
505 (if dup (setq acopy (format-delq-cons tail acopy)
506 bcopy (format-delq-cons dup bcopy)))
507 (setq tail next)))
508 (cons acopy bcopy)))
509
510(defun format-common-tail (a b)
511 "Given two lists that have a common tail, return it.
512Compares with `equal', and returns the part of A that is equal to the
513equivalent part of B. If even the last items of the two are not equal,
514returns nil."
515 (let ((la (length a))
516 (lb (length b)))
517 ;; Make sure they are the same length
823139fb 518 (if (> la lb)
029894b9
BG
519 (setq a (nthcdr (- la lb) a))
520 (setq b (nthcdr (- lb la) b))))
521 (while (not (equal a b))
522 (setq a (cdr a)
523 b (cdr b)))
524 a)
525
f0a6c717
GM
526(defun format-proper-list-p (list)
527 "Return t if LIST is a proper list.
528A proper list is a list ending with a nil cdr, not with an atom "
529 (when (listp list)
530 (while (consp list)
531 (setq list (cdr list)))
532 (null list)))
533
029894b9
BG
534(defun format-reorder (items order)
535 "Arrange ITEMS to following partial ORDER.
536Elements of ITEMS equal to elements of ORDER will be rearranged to follow the
537ORDER. Unmatched items will go last."
538 (if order
539 (let ((item (member (car order) items)))
540 (if item
823139fb 541 (cons (car item)
029894b9
BG
542 (format-reorder (format-delq-cons item items)
543 (cdr order)))
544 (format-reorder items (cdr order))))
545 items))
546
547(put 'face 'format-list-valued t) ; These text-properties take values
548(put 'unknown 'format-list-valued t) ; that are lists, the elements of which
549 ; should be considered separately.
550 ; See format-deannotate-region and
551 ; format-annotate-region.
552
f3bbef87
GM
553;; This text property has list values, but they are treated atomically.
554
555(put 'display 'format-list-atomic-p t)
556
029894b9
BG
557;;;
558;;; Decoding
559;;;
560
561(defun format-deannotate-region (from to translations next-fn)
562 "Translate annotations in the region into text properties.
823139fb 563This sets text properties between FROM to TO as directed by the
029894b9
BG
564TRANSLATIONS and NEXT-FN arguments.
565
566NEXT-FN is a function that searches forward from point for an annotation.
567It should return a list of 4 elements: \(BEGIN END NAME POSITIVE). BEGIN and
568END are buffer positions bounding the annotation, NAME is the name searched
569for in TRANSLATIONS, and POSITIVE should be non-nil if this annotation marks
570the beginning of a region with some property, or nil if it ends the region.
571NEXT-FN should return nil if there are no annotations after point.
572
573The basic format of the TRANSLATIONS argument is described in the
574documentation for the `format-annotate-region' function. There are some
575additional things to keep in mind for decoding, though:
576
577When an annotation is found, the TRANSLATIONS list is searched for a
578text-property name and value that corresponds to that annotation. If the
579text-property has several annotations associated with it, it will be used only
580if the other annotations are also in effect at that point. The first match
581found whose annotations are all present is used.
582
583The text property thus determined is set to the value over the region between
584the opening and closing annotations. However, if the text-property name has a
585non-nil `format-list-valued' property, then the value will be consed onto the
586surrounding value of the property, rather than replacing that value.
587
588There are some special symbols that can be used in the \"property\" slot of
589the TRANSLATIONS list: PARAMETER and FUNCTION \(spelled in uppercase).
590Annotations listed under the pseudo-property PARAMETER are considered to be
591arguments of the immediately surrounding annotation; the text between the
592opening and closing parameter annotations is deleted from the buffer but saved
92308d3a
RS
593as a string.
594
595The surrounding annotation should be listed under the pseudo-property
596FUNCTION. Instead of inserting a text-property for this annotation,
597the function listed in the VALUE slot is called to make whatever
598changes are appropriate. It can also return a list of the form
599\(START LOC PROP VALUE) which specifies a property to put on. The
600function's first two arguments are the START and END locations, and
601the rest of the arguments are any PARAMETERs found in that region.
029894b9
BG
602
603Any annotations that are found by NEXT-FN but not defined by TRANSLATIONS
604are saved as values of the `unknown' text-property \(which is list-valued).
605The TRANSLATIONS list should usually contain an entry of the form
606 \(unknown \(nil format-annotate-value))
607to write these unknown annotations back into the file."
608 (save-excursion
609 (save-restriction
610 (narrow-to-region (point-min) to)
611 (goto-char from)
612 (let (next open-ans todo loc unknown-ans)
613 (while (setq next (funcall next-fn))
614 (let* ((loc (nth 0 next))
615 (end (nth 1 next))
616 (name (nth 2 next))
617 (positive (nth 3 next))
618 (found nil))
619
620 ;; Delete the annotation
621 (delete-region loc end)
22828206
RS
622 (cond
623 ;; Positive annotations are stacked, remembering location
be227f22 624 (positive (push `(,name ((,loc . nil))) open-ans))
22828206
RS
625 ;; It is a negative annotation:
626 ;; Close the top annotation & add its text property.
627 ;; If the file's nesting is messed up, the close might not match
628 ;; the top thing on the open-annotations stack.
629 ;; If no matching annotation is open, just ignore the close.
630 ((not (assoc name open-ans))
631 (message "Extra closing annotation (%s) in file" name))
632 ;; If one is open, but not on the top of the stack, close
633 ;; the things in between as well. Set `found' when the real
634 ;; one is closed.
635 (t
636 (while (not found)
637 (let* ((top (car open-ans)) ; first on stack: should match.
638 (top-name (car top)) ; text property name
639 (top-extents (nth 1 top)) ; property regions
640 (params (cdr (cdr top))) ; parameters
641 (aalist translations)
642 (matched nil))
643 (if (equal name top-name)
644 (setq found t)
645 (message "Improper nesting in file."))
646 ;; Look through property names in TRANSLATIONS
647 (while aalist
648 (let ((prop (car (car aalist)))
649 (alist (cdr (car aalist))))
650 ;; And look through values for each property
651 (while alist
652 (let ((value (car (car alist)))
653 (ans (cdr (car alist))))
654 (if (member top-name ans)
655 ;; This annotation is listed, but still have to
656 ;; check if multiple annotations are satisfied
657 (if (member nil (mapcar (lambda (r)
658 (assoc r open-ans))
659 ans))
660 nil ; multiple ans not satisfied
661 ;; If there are multiple annotations going
662 ;; into one text property, split up the other
663 ;; annotations so they apply individually to
664 ;; the other regions.
665 (setcdr (car top-extents) loc)
666 (let ((to-split ans) this-one extents)
667 (while to-split
668 (setq this-one
669 (assoc (car to-split) open-ans)
670 extents (nth 1 this-one))
671 (if (not (eq this-one top))
672 (setcar (cdr this-one)
673 (format-subtract-regions
674 extents top-extents)))
675 (setq to-split (cdr to-split))))
676 ;; Set loop variables to nil so loop
677 ;; will exit.
678 (setq alist nil aalist nil matched t
679 ;; pop annotation off stack.
680 open-ans (cdr open-ans))
681 (let ((extents top-extents)
682 (start (car (car top-extents)))
683 (loc (cdr (car top-extents))))
684 (while extents
685 (cond
686 ;; Check for pseudo-properties
687 ((eq prop 'PARAMETER)
688 ;; A parameter of the top open ann:
689 ;; delete text and use as arg.
690 (if open-ans
691 ;; (If nothing open, discard).
692 (setq open-ans
693 (cons
694 (append (car open-ans)
695 (list
696 (buffer-substring
697 start loc)))
698 (cdr open-ans))))
699 (delete-region start loc))
700 ((eq prop 'FUNCTION)
701 ;; Not a property, but a function.
702 (let ((rtn
703 (apply value start loc params)))
be227f22 704 (if rtn (push rtn todo))))
22828206
RS
705 (t
706 ;; Normal property/value pair
707 (setq todo
708 (cons (list start loc prop value)
709 todo))))
710 (setq extents (cdr extents)
711 start (car (car extents))
712 loc (cdr (car extents))))))))
713 (setq alist (cdr alist))))
714 (setq aalist (cdr aalist)))
715 (if (not matched)
029894b9
BG
716 ;; Didn't find any match for the annotation:
717 ;; Store as value of text-property `unknown'.
22828206
RS
718 (let ((extents top-extents)
719 (start (car (car top-extents)))
0b140219 720 (loc (or (cdr (car top-extents)) loc)))
22828206
RS
721 (while extents
722 (setq open-ans (cdr open-ans)
723 todo (cons (list start loc 'unknown top-name)
724 todo)
725 unknown-ans (cons name unknown-ans)
726 extents (cdr extents)
727 start (car (car extents))
728 loc (cdr (car extents))))))))))))
029894b9
BG
729
730 ;; Once entire file has been scanned, add the properties.
731 (while todo
732 (let* ((item (car todo))
733 (from (nth 0 item))
734 (to (nth 1 item))
735 (prop (nth 2 item))
736 (val (nth 3 item)))
22828206
RS
737
738 (if (numberp val) ; add to ambient value if numeric
739 (format-property-increment-region from to prop val 0)
740 (put-text-property
029894b9 741 from to prop
22828206 742 (cond ((get prop 'format-list-valued) ; value gets consed onto
029894b9
BG
743 ; list-valued properties
744 (let ((prev (get-text-property from prop)))
745 (cons val (if (listp prev) prev (list prev)))))
22828206 746 (t val))))) ; normally, just set to val.
029894b9 747 (setq todo (cdr todo)))
22828206 748
029894b9
BG
749 (if unknown-ans
750 (message "Unknown annotations: %s" unknown-ans))))))
751
22828206 752(defun format-subtract-regions (minu subtra)
823139fb 753 "Remove from the regions in MINUend the regions in SUBTRAhend.
d0fc7e3b 754A region is a dotted pair (FROM . TO). Both parameters are lists of
823139fb
DL
755regions. Each list must contain nonoverlapping, noncontiguous
756regions, in descending order. The result is also nonoverlapping,
757noncontiguous, and in descending order. The first element of MINUEND
758can have a cdr of nil, indicating that the end of that region is not
759yet known."
22828206
RS
760 (let* ((minuend (copy-alist minu))
761 (subtrahend (copy-alist subtra))
762 (m (car minuend))
763 (s (car subtrahend))
764 results)
765 (while (and minuend subtrahend)
823139fb 766 (cond
22828206
RS
767 ;; The minuend starts after the subtrahend ends; keep it.
768 ((> (car m) (cdr s))
be227f22
SM
769 (push m results)
770 (setq minuend (cdr minuend)
22828206
RS
771 m (car minuend)))
772 ;; The minuend extends beyond the end of the subtrahend. Chop it off.
773 ((or (null (cdr m)) (> (cdr m) (cdr s)))
be227f22 774 (push (cons (1+ (cdr s)) (cdr m)) results)
22828206
RS
775 (setcdr m (cdr s)))
776 ;; The subtrahend starts after the minuend ends; throw it away.
777 ((< (cdr m) (car s))
778 (setq subtrahend (cdr subtrahend) s (car subtrahend)))
779 ;; The subtrahend extends beyond the end of the minuend. Chop it off.
780 (t ;(<= (cdr m) (cdr s)))
781 (if (>= (car m) (car s))
782 (setq minuend (cdr minuend) m (car minuend))
783 (setcdr m (1- (car s)))
784 (setq subtrahend (cdr subtrahend) s (car subtrahend))))))
785 (nconc (nreverse results) minuend)))
786
787;; This should probably go somewhere other than format.el. Then again,
788;; indent.el has alter-text-property. NOTE: We can also use
789;; next-single-property-change instead of text-property-not-all, but then
790;; we have to see if we passed TO.
791(defun format-property-increment-region (from to prop delta default)
823139fb
DL
792 "Over the region between FROM and TO increment property PROP by amount DELTA.
793DELTA may be negative. If property PROP is nil anywhere
22828206
RS
794in the region, it is treated as though it were DEFAULT."
795 (let ((cur from) val newval next)
796 (while cur
797 (setq val (get-text-property cur prop)
798 newval (+ (or val default) delta)
799 next (text-property-not-all cur to prop val))
800 (put-text-property cur (or next to) prop newval)
801 (setq cur next))))
802
029894b9
BG
803;;;
804;;; Encoding
805;;;
806
807(defun format-insert-annotations (list &optional offset)
808 "Apply list of annotations to buffer as `write-region' would.
809Inserts each element of the given LIST of buffer annotations at its
810appropriate place. Use second arg OFFSET if the annotations' locations are
811not relative to the beginning of the buffer: annotations will be inserted
812at their location-OFFSET+1 \(ie, the offset is treated as the character number
813of the first character in the buffer)."
823139fb 814 (if (not offset)
029894b9
BG
815 (setq offset 0)
816 (setq offset (1- offset)))
817 (let ((l (reverse list)))
818 (while l
819 (goto-char (- (car (car l)) offset))
820 (insert (cdr (car l)))
821 (setq l (cdr l)))))
822
823(defun format-annotate-value (old new)
0b1ce6ba 824 "Return OLD and NEW as a \(CLOSE . OPEN) annotation pair.
029894b9
BG
825Useful as a default function for TRANSLATIONS alist when the value of the text
826property is the name of the annotation that you want to use, as it is for the
827`unknown' text property."
828 (cons (if old (list old))
829 (if new (list new))))
830
823139fb 831(defun format-annotate-region (from to translations format-fn ignore)
029894b9
BG
832 "Generate annotations for text properties in the region.
833Searches for changes between FROM and TO, and describes them with a list of
834annotations as defined by alist TRANSLATIONS and FORMAT-FN. IGNORE lists text
835properties not to consider; any text properties that are neither ignored nor
836listed in TRANSLATIONS are warned about.
837If you actually want to modify the region, give the return value of this
838function to `format-insert-annotations'.
839
840Format of the TRANSLATIONS argument:
841
842Each element is a list whose car is a PROPERTY, and the following
d0fc7e3b
RS
843elements have the form (VALUE ANNOTATIONS...).
844Whenever the property takes on the value VALUE, the annotations
029894b9
BG
845\(as formatted by FORMAT-FN) are inserted into the file.
846When the property stops having that value, the matching negated annotation
847will be inserted \(it may actually be closed earlier and reopened, if
823139fb 848necessary, to keep proper nesting).
029894b9 849
d0fc7e3b 850If VALUE is a list, then each element of the list is dealt with
029894b9
BG
851separately.
852
853If a VALUE is numeric, then it is assumed that there is a single annotation
854and each occurrence of it increments the value of the property by that number.
855Thus, given the entry \(left-margin \(4 \"indent\")), if the left margin
856changes from 4 to 12, two <indent> annotations will be generated.
857
858If the VALUE is nil, then instead of annotations, a function should be
859specified. This function is used as a default: it is called for all
860transitions not explicitly listed in the table. The function is called with
861two arguments, the OLD and NEW values of the property. It should return
d0fc7e3b 862a cons cell (CLOSE . OPEN) as `format-annotate-single-property-change' does.
029894b9 863
d0fc7e3b 864The same TRANSLATIONS structure can be used in reverse for reading files."
029894b9
BG
865 (let ((all-ans nil) ; All annotations - becomes return value
866 (open-ans nil) ; Annotations not yet closed
867 (loc nil) ; Current location
868 (not-found nil)) ; Properties that couldn't be saved
869 (while (or (null loc)
870 (and (setq loc (next-property-change loc nil to))
871 (< loc to)))
872 (or loc (setq loc from))
823139fb 873 (let* ((ans (format-annotate-location loc (= loc from) ignore translations))
029894b9
BG
874 (neg-ans (format-reorder (aref ans 0) open-ans))
875 (pos-ans (aref ans 1))
876 (ignored (aref ans 2)))
877 (setq not-found (append ignored not-found)
878 ignore (append ignored ignore))
879 ;; First do the negative (closing) annotations
880 (while neg-ans
881 ;; Check if it's missing. This can happen (eg, a numeric property
882 ;; going negative can generate closing annotations before there are
883 ;; any open). Warn user & ignore.
884 (if (not (member (car neg-ans) open-ans))
885 (message "Can't close %s: not open." (car neg-ans))
886 (while (not (equal (car neg-ans) (car open-ans)))
887 ;; To close anno. N, need to first close ans 1 to N-1,
888 ;; remembering to re-open them later.
be227f22 889 (push (car open-ans) pos-ans)
823139fb 890 (setq all-ans
029894b9
BG
891 (cons (cons loc (funcall format-fn (car open-ans) nil))
892 all-ans))
893 (setq open-ans (cdr open-ans)))
894 ;; Now remove the one we're really interested in from open list.
895 (setq open-ans (cdr open-ans))
896 ;; And put the closing annotation here.
be227f22
SM
897 (push (cons loc (funcall format-fn (car neg-ans) nil))
898 all-ans))
029894b9
BG
899 (setq neg-ans (cdr neg-ans)))
900 ;; Now deal with positive (opening) annotations
901 (let ((p pos-ans))
902 (while pos-ans
be227f22
SM
903 (push (car pos-ans) open-ans)
904 (push (cons loc (funcall format-fn (car pos-ans) t))
905 all-ans)
029894b9
BG
906 (setq pos-ans (cdr pos-ans))))))
907
908 ;; Close any annotations still open
909 (while open-ans
823139fb 910 (setq all-ans
029894b9
BG
911 (cons (cons to (funcall format-fn (car open-ans) nil))
912 all-ans))
913 (setq open-ans (cdr open-ans)))
914 (if not-found
915 (message "These text properties could not be saved:\n %s"
916 not-found))
917 (nreverse all-ans)))
918
919;;; Internal functions for format-annotate-region.
920
823139fb
DL
921(defun format-annotate-location (loc all ignore translations)
922 "Return annotation(s) needed at location LOC.
029894b9
BG
923This includes any properties that change between LOC-1 and LOC.
924If ALL is true, don't look at previous location, but generate annotations for
925all non-nil properties.
926Third argument IGNORE is a list of text-properties not to consider.
d0fc7e3b 927Use the TRANSLATIONS alist (see `format-annotate-region' for doc).
029894b9
BG
928
929Return value is a vector of 3 elements:
d0fc7e3b
RS
9301. List of annotations to close
9312. List of annotations to open.
9323. List of properties that were ignored or couldn't be annotated.
933
934The annotations in lists 1 and 2 need not be strings.
935They can be whatever the FORMAT-FN in `format-annotate-region'
936can handle. If that is `enriched-make-annotation', they can be
937either strings, or lists of the form (PARAMETER VALUE)."
029894b9
BG
938 (let* ((prev-loc (1- loc))
939 (before-plist (if all nil (text-properties-at prev-loc)))
940 (after-plist (text-properties-at loc))
941 p negatives positives prop props not-found)
942 ;; make list of all property names involved
943 (setq p before-plist)
944 (while p
945 (if (not (memq (car p) props))
be227f22 946 (push (car p) props))
029894b9
BG
947 (setq p (cdr (cdr p))))
948 (setq p after-plist)
949 (while p
950 (if (not (memq (car p) props))
be227f22 951 (push (car p) props))
029894b9
BG
952 (setq p (cdr (cdr p))))
953
954 (while props
be227f22 955 (setq prop (pop props))
029894b9
BG
956 (if (memq prop ignore)
957 nil ; If it's been ignored before, ignore it now.
958 (let ((before (if all nil (car (cdr (memq prop before-plist)))))
959 (after (car (cdr (memq prop after-plist)))))
960 (if (equal before after)
961 nil ; no change; ignore
962 (let ((result (format-annotate-single-property-change
823139fb 963 prop before after translations)))
029894b9 964 (if (not result)
be227f22 965 (push prop not-found)
029894b9
BG
966 (setq negatives (nconc negatives (car result))
967 positives (nconc positives (cdr result)))))))))
968 (vector negatives positives not-found)))
969
d0fc7e3b 970(defun format-annotate-single-property-change (prop old new translations)
823139fb 971 "Return annotations for property PROP changing from OLD to NEW.
d0fc7e3b
RS
972These are searched for in the translations alist TRANSLATIONS
973 (see `format-annotate-region' for the format).
029894b9
BG
974If NEW does not appear in the list, but there is a default function, then that
975function is called.
d0fc7e3b
RS
976Returns a cons of the form (CLOSE . OPEN)
977where CLOSE is a list of annotations to close
978and OPEN is a list of annotations to open.
979
980The annotations in CLOSE and OPEN need not be strings.
981They can be whatever the FORMAT-FN in `format-annotate-region'
982can handle. If that is `enriched-make-annotation', they can be
983either strings, or lists of the form (PARAMETER VALUE)."
984
985 (let ((prop-alist (cdr (assoc prop translations)))
029894b9
BG
986 default)
987 (if (not prop-alist)
988 nil
029894b9 989 ;; If either old or new is a list, have to treat both that way.
f0a6c717 990 (if (and (or (listp old) (listp new))
f3bbef87 991 (not (get prop 'format-list-atomic-p)))
f0a6c717
GM
992 (if (or (not (format-proper-list-p old))
993 (not (format-proper-list-p new)))
994 (format-annotate-atomic-property-change prop-alist old new)
995 (let* ((old (if (listp old) old (list old)))
996 (new (if (listp new) new (list new)))
997 (tail (format-common-tail old new))
998 close open)
999 (while old
1000 (setq close
1001 (append (car (format-annotate-atomic-property-change
1002 prop-alist (car old) nil))
1003 close)
1004 old (cdr old)))
1005 (while new
1006 (setq open
1007 (append (cdr (format-annotate-atomic-property-change
1008 prop-alist nil (car new)))
1009 open)
1010 new (cdr new)))
1011 (format-make-relatively-unique close open)))
029894b9
BG
1012 (format-annotate-atomic-property-change prop-alist old new)))))
1013
1014(defun format-annotate-atomic-property-change (prop-alist old new)
1015 "Internal function annotate a single property change.
d0fc7e3b 1016PROP-ALIST is the relevant element of a TRANSLATIONS list.
029894b9 1017OLD and NEW are the values."
e5a60108
RS
1018 (let (num-ann)
1019 ;; If old and new values are numbers,
1020 ;; look for a number in PROP-ALIST.
5f525595
RS
1021 (if (and (or (null old) (numberp old))
1022 (or (null new) (numberp new)))
e5a60108
RS
1023 (progn
1024 (setq num-ann prop-alist)
1025 (while (and num-ann (not (numberp (car (car num-ann)))))
1026 (setq num-ann (cdr num-ann)))))
1027 (if num-ann
5f525595 1028 ;; Numerical annotation - use difference
34fbecde
RS
1029 (progn
1030 ;; If property is numeric, nil means 0
1031 (cond ((and (numberp old) (null new))
1032 (setq new 0))
1033 ((and (numberp new) (null old))
1034 (setq old 0)))
1035
1036 (let* ((entry (car num-ann))
1037 (increment (car entry))
1038 (n (ceiling (/ (float (- new old)) (float increment))))
1039 (anno (car (cdr entry))))
1040 (if (> n 0)
1041 (cons nil (make-list n anno))
1042 (cons (make-list (- n) anno) nil))))
e5a60108
RS
1043
1044 ;; Standard annotation
1045 (let ((close (and old (cdr (assoc old prop-alist))))
029894b9
BG
1046 (open (and new (cdr (assoc new prop-alist)))))
1047 (if (or close open)
1048 (format-make-relatively-unique close open)
1049 ;; Call "Default" function, if any
1050 (let ((default (assq nil prop-alist)))
1051 (if default
1052 (funcall (car (cdr default)) old new))))))))
1053
c84cf2bf 1054(provide 'format)
823139fb 1055
ab5796a9 1056;;; arch-tag: c387e9c7-a93d-47bf-89bc-8ca67e96755a
823139fb 1057;;; format.el ends here