33.34.1 Отслеживание модификаций буфера

Использование before-change-functions и after-change-functions на практике может быть затруднительным из-за ряда подводных камней, таких как тот факт, что два вызова не всегда правильно сопоставляются, или некоторые вызовы могут отсутствовать, либо из-за того, что некоторые примитивы Emacs не смогли их правильно сопоставить, либо из-за некорректного использования inhibit-modification-hooks. Кроме того, к этим хукам применяется множество ограничений, например, то, что они практически никогда не должны изменять текущий буфер, использовать операцию, которая может заблокироваться, и выполняться быстро, поскольку некоторые команды могут вызывать эти хуки очень большое количество раз.

Библиотека Track-Changes предлагает принципиально иной API, построенный поверх этих хуков. По сравнению с after-change-functions, первое важное отличие заключается в том, что вместо границ изменения и предыдущей длины она предоставляет границы изменения и фактическое предыдущее содержимое этой области. Необходимость извлекать информацию из исходного содержимого буфера является одной из главных причин, по которым некоторым пакетам требуется использовать как before-change-functions, так и after-change-functions, а затем пытаться сопоставлять их.

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

Чтобы начать отслеживание изменений, необходимо вызвать track-changes-register, передав ему функцию signal в качестве аргумента. Она возвращает идентификатор id трекера, который используется для идентификации вашего отслеживателя изменений в других функциях библиотеки. Когда буфер изменяется, библиотека вызывает функцию signal, чтобы уведомить вас об этом изменении, и сразу же начинает накапливать последующие изменения в одно объединённое изменение. Функция signal служит только для предупреждения о том, что произошла модификация, но не получает описания изменения. Кроме того, библиотека не будет вызывать её снова, пока вы не получите изменение.

Чтобы получить изменения, нужно вызвать track-changes-fetch, которая предоставляет вам границы изменений, накопленных с момента последнего вызова, а также предыдущее содержимое этой области. Она также «перезаряжает» функцию signal, чтобы библиотека снова вызвала её после следующей модификации буфера.

Функция: track-changes-register signal &key nobefore disjoint immediate

Эта функция создаёт новый отслеживатель изменений. Отслеживатели изменений остаются абстрактными, поэтому мы ссылаемся на них как на простые идентификаторы, и функция возвращает id трекера.

signal — это функция, которую библиотека будет вызывать для уведомления об изменении. Иногда она будет вызываться с одним аргументом, а иногда с двумя. При первом изменении буфера с момента последнего вызова этим трекером track-changes-fetch, библиотека вызывает эту функцию signal с одним аргументом, содержащим id трекера.

По умолчанию вызов функции signal происходит не немедленно, а откладывается с помощью таймера на 0 секунд (см. Таймеры для отложенного выполнения). Это обычно желательно, чтобы гарантировать, что функция signal вызывается не слишком часто и выполняется в разрешающем контексте, освобождая клиента от проблем с производительностью или беспокойства о том, какие операции могут быть проблематичными. Если клиент хочет иметь больше контроля, он может передать не nil значение в качестве аргумента immediate, и в этом случае библиотека вызывает функцию signal напрямую из after-change-functions. Имейте в виду, что это означает, что функция signal должна быть осторожна, чтобы не изменять буфер или не использовать операции, которые могут заблокироваться.

Если вас не интересует фактическое предыдущее содержимое буфера, но вы используете эту библиотеку только из-за её способности объединять много мелких изменений в одно более крупное и откладывать обработку до более удобного времени, вы можете указать не nil значение для аргумента nobefore. В этом случае track-change-fetch предоставляет вам только длину предыдущего содержимого, как и after-change-functions. Это также позволяет библиотеке сэкономить немного работы.

Хотя вам может нравиться накапливать много мелких изменений в более крупные, вы можете не захотеть этого делать, если изменения находятся слишком далеко друг от друга. Если вы укажете не nil значение для аргумента disjoint, библиотека сообщит вам, когда изменение вот-вот произойдёт «далеко» от текущих ожидающих изменений, вызвав функцию signal немедленно, передав ей на этот раз два аргумента: id трекера и количество символов, отделяющих предстоящее изменение от уже ожидающих изменений. Это само по себе не препятствует объединению этого нового изменения с предыдущими, поэтому если вы считаете, что предстоящее изменение действительно слишком далеко, вам нужно немедленно вызвать track-change-fetch. Имейте в виду, что когда функция signal вызывается из-за разрозненного изменения, это происходит непосредственно из before-change-functions, поэтому применяются обычные ограничения, касающиеся изменения буфера или использования операций, которые могут заблокироваться.

Функция: track-changes-fetch id func

Это функция, которая позволяет вам узнать, что изменилось в буфере. Предоставляя идентификатор трекера id, вы даёте библиотеке возможность определить, какие изменения уже были обработаны вашим трекером. Вместо возврата описания изменений track-changes-fetch вызывает функцию func с этим описанием в виде трёх аргументов: beg, end и before, где beg..end ограничивают область, которая была изменена, а before описывает предыдущее содержимое этой области. Обычно before — это строка, содержащая предыдущий текст изменённой области, но если вы указали не nil аргумент nobefore для track-changes-register, то вместо неё передаётся количество символов этого предыдущего текста.

В случае, если изменений с момента последнего вызова не произошло, track-changes-fetch просто не вызывает func и возвращает nil. Если изменения произошли, она вызывает func и возвращает значение, возвращённое func. Но обратите внимание, что func вызывается только один раз независимо от того, сколько изменений произошло: они обобщаются в один тройной набор beg/end/before.

В некоторых случаях библиотека не получает надлежащего уведомления обо всех изменениях, например, из-за ошибки в низкоуровневом коде на C или из-за некорректного использования inhibit-modification-hooks. Когда она обнаруживает такую проблему, func получает область beg..end, которая покрывает весь буфер, а аргумент before принимает символ error, указывающий, что библиотеке не удалось определить, что было изменено.

Как только func завершает работу, track-changes-fetch повторно включает функцию signal, чтобы она была вызвана при следующем изменении. Это причина, по которой она вызывает func вместо возврата описания: это позволяет вам обработать изменение, не беспокоясь о риске того, что функция signal сработает в середине этого процесса, поскольку signal повторно включается только после завершения func.

Функция: track-changes-unregister id

Эта функция сообщает библиотеке, что трекер id больше не нуждается в уведомлениях об изменениях буфера. Большинство клиентов никогда не захотят прекращать отслеживание изменений, но для таких клиентов, как второстепенные режимы, важно вызывать эту функцию при отключении второстепенного режима, иначе трекер будет продолжать накапливать изменения и потреблять всё больше ресурсов.