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