26.13.3 Поэтапная спецификация

В отличие от спецификации обратного прохода, описанной в предыдущем разделе (см. Спецификация обратного прохода), вы можете использовать переменные after-insert-file-functions и write-region-annotate-functions, чтобы отдельно контролировать соответствующие операции чтения и записи.

Преобразование начинается с одного представления и приводит к другому представлению. Когда необходимо выполнить только одно преобразование, нет конфликта относительно того, с чего начать. Однако, когда задействовано несколько преобразований, может возникнуть конфликт, если два преобразования должны начать с одних и тех же данных.

Эта ситуация лучше всего понимается в контексте преобразования текстовых свойств во время write-region. Например, символ в позиции 42 в буфере — это «X» с текстовым свойством foo. Если преобразование для foo выполняется путем вставки в буфер, скажем, «FOO:», то это изменяет символ в позиции 42 с «X» на «F». Следующее преобразование сразу же начнется с неверных данных.

Чтобы избежать конфликта, кооперативные преобразования не изменяют буфер, а вместо этого указывают аннотации, список элементов вида (position . string), отсортированных по возрастанию position.

Если преобразований более одного, write-region деструктивно объединяет их аннотации в один отсортированный список. Позже, когда текст из буфера фактически записывается в файл, он смешивает указанные аннотации в соответствующих позициях. Все это происходит без изменения буфера.

В отличие от этого, при чтении аннотации, смешанные с текстом, обрабатываются немедленно. insert-file-contents устанавливает точку в начало некоторого текста, который необходимо преобразовать, а затем вызывает функции преобразования с длиной этого текста. Эти функции всегда должны возвращать с точкой в начале вставленного текста. Такой подход имеет смысл для чтения, поскольку аннотации, удаленные первым преобразователем, не могут быть ошибочно обработаны последующим преобразователем. Каждая функция преобразования должна сканировать аннотации, которые она распознает, удалить аннотацию, изменить текст буфера (например, установить текстовое свойство) и вернуть обновленную длину текста, как он выглядит после этих изменений. Значение, возвращенное одной функцией, становится аргументом для следующей функции.

Переменная: write-region-annotate-functions

Список функций, которые write-region должен вызвать. Каждая функция в списке вызывается с двумя аргументами: началом и концом области, которую необходимо записать. Эти функции не должны изменять содержимое буфера. Вместо этого они должны возвращать аннотации.

В качестве особого случая функция может вернуть с другим текущим буфером. Emacs интерпретирует это как то, что текущий буфер содержит измененный текст для вывода. Поэтому он изменяет аргументы start и end вызова write-region, присваивая им значения point-min и point-max в новом буфере соответственно. Он также отбрасывает все предыдущие аннотации, поскольку они должны были быть обработаны этой функцией.

Переменная: write-region-post-annotation-function

Значение этой переменной, если оно не nil, должно быть функцией. Эта функция вызывается без аргументов после завершения write-region.

Если любая функция в write-region-annotate-functions возвращает с другим текущим буфером, Emacs вызывает write-region-post-annotation-function более одного раза. Emacs вызывает ее с последним текущим буфером, а затем с буфером до него и так далее до исходного буфера.

Таким образом, функция в write-region-annotate-functions может создать буфер, присвоить этой переменной локальное значение kill-buffer в этом буфере, настроить буфер измененным текстом и сделать буфер текущим. Буфер будет уничтожен после завершения write-region.

Переменная: after-insert-file-functions

Каждая функция в этом списке вызывается insert-file-contents с одним аргументом — количеством вставленных символов — и с точкой в начале вставленного текста. Каждая функция должна оставить точку без изменений и вернуть новое количество символов, описывающее вставленный текст после изменений, внесенных функцией.

Мы призываем пользователей писать программы на Lisp для хранения и извлечения текстовых свойств в файлах, используя эти хуки, и таким образом экспериментировать с различными форматами данных и находить хорошие. В конечном итоге мы надеемся, что пользователи создадут хорошие, общие расширения, которые мы сможем установить в Emacs.

Мы советуем не пытаться обрабатывать произвольные объекты Lisp как имена или значения текстовых свойств, поскольку такая общая программа, вероятно, будет трудно написана и медленной. Вместо этого выберите набор возможных типов данных, которые являются достаточно гибкими и не слишком сложными для кодирования.