In contrast to the round-trip specification described in the previous
subsection (see Round-Trip Specification), you can use the variables
after-insert-file-functions
and write-region-annotate-functions
to separately control the respective reading and writing conversions.
Conversion starts with one representation and produces another representation. When there is only one conversion to do, there is no conflict about what to start with. However, when there are multiple conversions involved, conflict may arise when two conversions need to start with the same data.
This situation is best understood in the context of converting text
properties during write-region
. For example, the character at
position 42 in a buffer is ‘X’ with a text property foo
. If
the conversion for foo
is done by inserting into the buffer, say,
‘FOO:’, then that changes the character at position 42 from
‘X’ to ‘F’. The next conversion will start with the wrong
data straight away.
To avoid conflict, cooperative conversions do not modify the buffer,
but instead specify annotations, a list of elements of the form
(position . string)
, sorted in order of increasing
position.
If there is more than one conversion, write-region
merges their
annotations destructively into one sorted list. Later, when the text
from the buffer is actually written to the file, it intermixes the
specified annotations at the corresponding positions. All this takes
place without modifying the buffer.
In contrast, when reading, the annotations intermixed with the text
are handled immediately. insert-file-contents
sets point to
the beginning of some text to be converted, then calls the conversion
functions with the length of that text. These functions should always
return with point at the beginning of the inserted text. This
approach makes sense for reading because annotations removed by the
first converter can’t be mistakenly processed by a later converter.
Each conversion function should scan for the annotations it
recognizes, remove the annotation, modify the buffer text (to set a
text property, for example), and return the updated length of the
text, as it stands after those changes. The value returned by one
function becomes the argument to the next function.
A list of functions for write-region
to call. Each function in
the list is called with two arguments: the start and end of the region
to be written. These functions should not alter the contents of the
buffer. Instead, they should return annotations.
As a special case, a function may return with a different buffer
current. Emacs takes this to mean that the current buffer contains
altered text to be output. It therefore changes the start and
end arguments of the write-region
call, giving them the
values of point-min
and point-max
in the new buffer,
respectively. It also discards all previous annotations, because they
should have been dealt with by this function.
The value of this variable, if non-nil
, should be a function.
This function is called, with no arguments, after write-region
has completed.
If any function in write-region-annotate-functions
returns with
a different buffer current, Emacs calls
write-region-post-annotation-function
more than once. Emacs
calls it with the last buffer that was current, and again with the
buffer before that, and so on back to the original buffer.
Thus, a function in write-region-annotate-functions
can create
a buffer, give this variable the local value of kill-buffer
in
that buffer, set up the buffer with altered text, and make the buffer
current. The buffer will be killed after write-region
is done.
Each function in this list is called by insert-file-contents
with one argument, the number of characters inserted, and with point
at the beginning of the inserted text. Each function should leave
point unchanged, and return the new character count describing the
inserted text as modified by the function.
We invite users to write Lisp programs to store and retrieve text properties in files, using these hooks, and thus to experiment with various data formats and find good ones. Eventually we hope users will produce good, general extensions we can install in Emacs.
We suggest not trying to handle arbitrary Lisp objects as text property names or values—because a program that general is probably difficult to write, and slow. Instead, choose a set of possible data types that are reasonably flexible, and not too hard to encode.