42.4.2 Отображение хода выполнения операции

Когда операция может выполняться долго, следует информировать пользователя о ходе её выполнения. Так пользователь сможет оценить оставшееся время и наглядно увидеть, что Emacs занят работой, а не завис. Удобный способ сделать это — использовать репортёр прогресса.

Вот рабочий пример, который не делает ничего полезного:

(let ((progress-reporter
       (make-progress-reporter "Собираем ману для Emacs..."
                               0  500)))
  (dotimes (k 500)
    (sit-for 0.01)
    (progress-reporter-update progress-reporter k))
  (progress-reporter-done progress-reporter))
Функция: make-progress-reporter message &optional min-value max-value current-value min-change min-time

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

При последующем использовании этого репортёра прогресса он будет отображать message в области эхо, сопровождая его процентом выполнения. message обрабатывается как простая строка. Если нужно, чтобы она зависела, например, от имени файла, используйте format-message перед вызовом этой функции.

Аргументы min-value и max-value должны быть числами, обозначающими начальное и конечное состояния операции. Например, операция сканирования буфера должна установить их в результаты point-min и point-max соответственно. max-value должно быть больше min-value.

В качестве альтернативы можно задать min-value и max-value как nil. В этом случае репортёр прогресса не сообщает проценты выполнения; вместо этого он отображает «спиннер», который поворачивает насечку при каждом обновлении репортёра.

Если min-value и max-value являются числами, вы можете задать аргументу current-value числовое значение, указывающее начальный прогресс; если он опущен, по умолчанию используется min-value.

Остальные аргументы управляют частотой обновления области эхо. Репортёр прогресса будет ждать, пока не будет выполнено как минимум min-change процентов операции, прежде чем выводить следующее сообщение; по умолчанию это один процент. min-time задаёт минимальное время в секундах между последовательными выводами; по умолчанию это 0,2 секунды. (На некоторых операционных системах репортёр прогресса может обрабатывать доли секунды с различной точностью.)

Эта функция вызывает progress-reporter-update, поэтому первое сообщение выводится немедленно.

Функция: progress-reporter-update reporter &optional value suffix

Эта функция выполняет основную работу по сообщению о ходе выполнения вашей операции. Она отображает сообщение reporter, сопровождаемое процентом выполнения, определяемым value. Если процент равен нулю или достаточно близок к нему согласно аргументам min-change и min-time, то он опускается в выводе.

reporter должен быть результатом вызова make-progress-reporter. value задаёт текущее состояние вашей операции и должно находиться между min-value и max-value (включительно), переданными в make-progress-reporter. Например, если вы сканируете буфер, то value должно быть результатом вызова point.

Необязательный аргумент suffix — это строка, которая будет отображаться после основного сообщения reporter и текста прогресса. Если reporter является нечисловым репортёром, то value должно быть nil или строкой, которая будет использоваться вместо suffix.

Эта функция учитывает min-change и min-time, переданные в make-progress-reporter, и поэтому не выводит новые сообщения при каждом вызове. Таким образом, она очень быстрая, и обычно вам не следует пытаться уменьшать количество вызовов к ней: возникающие накладные расходы, скорее всего, сведут на нет ваши усилия.

Функция: progress-reporter-force-update reporter &optional value new-message suffix

Эта функция аналогична progress-reporter-update, за исключением того, что она безусловно выводит сообщение в область эхо.

reporter, value и suffix имеют тот же смысл, что и для progress-reporter-update. Необязательный new-message позволяет изменить сообщение reporter. Поскольку эта функция всегда обновляет область эхо, такое изменение будет немедленно представлено пользователю.

Функция: progress-reporter-done reporter

Эту функцию следует вызывать, когда операция завершена. Она выводит в область эхо сообщение reporter, за которым следует слово «done».

Вы всегда должны вызывать эту функцию и не надеяться, что progress-reporter-update выведет «100%». Во-первых, она может никогда не вывести это, для этого есть много веских причин. Во-вторых, «done» более явно указывает на завершение.

Макрос: dotimes-with-progress-reporter (var count [result]) reporter-or-message body…

Это удобный макрос, который работает так же, как dotimes, но также сообщает о ходе выполнения цикла с помощью описанных выше функций. Он позволяет сэкономить немного набора. Аргумент reporter-or-message может быть либо строкой, либо объектом репортёра прогресса.

Вы можете переписать пример в начале этого подраздела с использованием этого макроса следующим образом:

(dotimes-with-progress-reporter
    (k 500)
    "Собираем немного маны для Emacs..."
  (sit-for 0.01))

Использование объекта репортёра в качестве аргумента reporter-or-message полезно, если вы хотите указать необязательные аргументы в make-progress-reporter. Например, вы можете записать предыдущий пример так:

(dotimes-with-progress-reporter
    (k 500)
    (make-progress-reporter "Собираем немного маны для Emacs..." 0 500 0 1 1.5)
  (sit-for 0.01))
Макрос: dolist-with-progress-reporter (var list [result]) reporter-or-message body…

Это ещё один удобный макрос, который работает так же, как dolist, но также сообщает о ходе выполнения цикла с помощью описанных выше функций. Как и в dotimes-with-progress-reporter, reporter-or-message может быть репортёром прогресса или строкой. Вы можете переписать предыдущий пример с помощью этого макроса так:

(dolist-with-progress-reporter
    (k (number-sequence 0 500))
    "Собираем немного маны для Emacs..."
  (sit-for 0.01))
Макрос: with-delayed-message (timeout message) body…

Иногда неясно, будет ли операция выполняться долго или нет, или реализация репортёра прогресса может быть неудобной. Этот макрос можно использовать в таких ситуациях.

(with-delayed-message (2 (format "Собираем данные для %s" entry))
  (setq data (gather-data entry)))

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

Элемент message вычисляется перед body и всегда вычисляется, независимо от того, отображается сообщение или нет.