[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Список удалений являeтся списком, который превращен в кольцо с помощью
функции rotate-yank-pointer
. Команды yank
и
yank-pop
используют функцию rotate-yank-pointer
. В этом
приложение описывается функция rotate-yank-pointer
, а также
команды yank
и yank-pop
.
B.1 Функция rotate-yank-pointer
Перемещает указатель по списку. B.2 yank
B.3 yank-pop
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
rotate-yank-pointer
Функция rotate-yank-pointer
изменяет элемент в списке удалений,
на который указывает kill-ring-yank-pointer
. Например, эта
функция может изменить kill-ring-yank-pointer
чтобы он указывал
на третий элемент, а не на второй.
Вот код функции rotate-yank-pointer
:
(defun rotate-yank-pointer (arg) "Циклическое вращение rotate-yank-pointer." (interactive "p") (let ((length (length kill-ring))) (if (zerop length) ;; then-часть (error "Kill ring is empty") ;; else-часть (setq kill-ring-yank-pointer (nthcdr (% (+ arg (- length (length kill-ring-yank-pointer))) length) kill-ring))))) |
Функция выглядит сложной, но как обычно, она может быть разобрана по частям. Сначала взгляним на ее скелет:
(defun rotate-yank-pointer (arg) "Циклическое вращение rotate-yank-pointer." (interactive "p") (let varlist body...) |
Эта функция принимает один аргумент, который называется arg
. Она
имеет короткую строку документации; и она интерактивная, используя
маленькую букву `p', которая означает, что аргумент должен быть
обработанным префиксом, который передается функции как число.
Телом определения функции является выражение let
, которое само имеет
тело и список переменных varlist.
Выражение let
объявляет переменную, которая будет использоваться
только в области видимости данной функции. Эта переменная имеет имя
length
и связана со значением равным количеству объектов в списке
удалений. Подсчет производится с помощью функции length
.
(Заметьте, что эта функция имеет тоже имя, что и переменная
length
; но в одном случае это слово используется для именования
функции, а во втором случае, для именования переменной. Эти два имени
достаточно отличаются. Аналогично, англоговорящие пользователи будут
различать значения слова `ship' когда они говорят: "I must ship
this package immediately." и "I must get aboard the ship immediately.")
Функция length
сообщает количество объектов в списке, так что
выражение (length kill-ring)
вернет количество объектов в списке
удалений.
B.1.1 Тело функции rotate-yank-pointer
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
rotate-yank-pointer
Тело функции rotate-yank-pointer
состоит из выражения let
, а
тело выражения let
состоит из выражения if
.
Выражение if
используется для определения того, если ли объекты в
списке удалений. Если список удалений пуст, то функция error
прекращает работу функции и выдает сообщение в области сообщений. Но если в
списке удалений есть объекты, то функция продолжает работу.
Вот if-часть и then-часть выражения if
:
(if (zerop length) ; if-часть (error "Kill ring is empty") ; then-часть ... |
Если в списке удалений ничего нет, то его длина должна быть равна нулю и
пользователю отправляется сообщение об ошибке: `Kill ring is
empty'. Выражение if
использует функцию zerop
, которая
возвращает истинное значение, если тестируемый объект равен нулю. Когда
тест zerop
возвращает истинное значение, то оценивается then-часть
выражения if
. then-часть--- это список, который начинается с
функции error
, которая похожа на функцию message
(see section 1.8.5 Функция message
), в том, что она выдает сообщение в область
сообщений. Однако, в добавлении к выдаче сообщения, error
также
останавливает вычисление функции, в которой она вызвана. В этом случае,
это означает, что остальная часть функции не будет выполнена в том
случае, если длина списка удалений равна нулю.
else-часть выражения if
Функция вычисления остатка %
Использование %
вrotate-yank-pointer
Указатель на последний элемент
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
if
else-часть выражения if
предназначена для установки значения
kill-ring-yank-pointer
для тех случаев, когда в списке удалений
что-то есть. Код выглядит примерно так:
(setq kill-ring-yank-pointer (nthcdr (% (+ arg (- length (length kill-ring-yank-pointer))) length) kill-ring))))) |
Этот код нуждается в тщательном рассмотрении. Очевидно, что
kill-ring-yank-pointer
устанавливается равным содержимому
CDR от списка уничтожений, с использованием функции
nthcdr
, которая была описана в предыдущих разделах.
(See section 8.5 copy-region-as-kill
.). Но как это делается?
До того, как мы будем рассматривать код, давайте рассмотрим назначение
функции rotate-yank-pointer
.
Функция rotate-yank-pointer
изменяет значение, на которое
указывает kill-ring-yank-pointer
. Если
kill-ring-yank-pointer
начинается с указания на первый элемент
списка, то вызов функции rotate-yank-pointer
заставит ее
указывать на второй элемент; а если kill-ring-yank-pointer
указывает на второй элемент, то вызов rotate-yank-pointer
заставит его указывать на третий элемент. (И если при вызове
rotate-yank-pointer
задан аргумент больший единици, то это
заставит указатель переместиться на несколько элементов).
Функция rotate-yank-pointer
использует setq
для задания
значения на которое указывает kill-ring-yank-pointer
. Если
kill-ring-yank-pointer
указывает на первый элемент списка
уничтожений, то, в простейшем случае, функция
rotate-yank-pointer
должна заставить указывать его на второй
элемент. Смотря с другой стороны, kill-ring-yank-pointer
должен
быть установлен в значение равное результату выполнения CDR для
списка уничтожений.
Так, с данными условиями,
(setq kill-ring-yank-pointer ("some text" "a different piece of text" "yet more text")) (setq kill-ring ("some text" "a different piece of text" "yet more text")) |
код должен сделать следующее:
(setq kill-ring-yank-pointer (cdr kill-ring)) |
В качестве результата kill-ring-yank-pointer
будет выглядеть
следующим образом:
kill-ring-yank-pointer => ("a different piece of text" "yet more text")) |
Настоящее выражение setq
использует функцию nthcdr
для
выполнения данной работы.
Как мы видели ранее (see section 7.3 nthcdr
), функция nthcdr
работает
путем последовательного выполнения CDR для списка --- она
берет CDR от CDR от CDR ...
Два следующих выражения дают одинаковый результат:
(setq kill-ring-yank-pointer (cdr kill-ring)) (setq kill-ring-yank-pointer (nthcdr 1 kill-ring)) |
Однако в функции rotate-yank-pointer
, первым аргументом для
nthcdr
является сложное выражение, с некоторым количеством
арифметических операций внутри него:
(% (+ arg (- length (length kill-ring-yank-pointer))) length) |
Как обычно, нам нужно сначала посмотреть в наиболее глубоко вставленное выражение, а затем подняться к охватывающим выражениям.
Наиболее глубоко вложенным выражением является (length
kill-ring-yank-pointer)
. Это выражение определяет длину текущего
значения kill-ring-yank-pointer
. (Помните, что
kill-ring-yank-pointer
--- имя переменной, чьим значением
является список).
Определение длины находится внутри выражения:
(- length (length kill-ring-yank-pointer)) |
В этом выражении, первое употребление length
относится к
переменной, которой присвоено значение длины списка удалений в
выражении let
в начале функции. (Кто-то может думать, что
функция была бы более понятной, если бы переменная length
была
бы названа length-of-kill-ring
; но если вы посмотрите в текст
функции, то увидите, что она настолько короткая, что именование
переменной length
не доставляет беспокойства, до тех пор пока
вы разбиваете функции на небольшие кусочки, как мы это делаем тут).
Так что строка (- length (length kill-ring-yank-pointer))
возвращает разницу между длиной списка удалений и длиной списка на
который указывает kill-ring-yank-pointer
.
Для того чтобы понять, как все это работает в функции
rotate-yank-pointer
, давайте рассмотрим случай, когда
kill-ring-yank-pointer
указывает на первый элемент списка
удалений как и kill-ring
и посмотрим что произойдет, если
функкцию rotate-yank-pointer
вызвали с аргументом 1.
В этом случае переменная length
и значение выражения
(length kill-ring-yank-pointer)
будут тем же самым так как
переменная length
это длина списка удалений, а
kill-ring-yank-pointer
указывает на весь список удалений.
Следовательно значение
(- length (length kill-ring-yank-pointer)) |
будет равно нулю. Так как значение arg
будет 1 это значит, что
значение всего выражения
(+ arg (- length (length kill-ring-yank-pointer))) |
будет 1.
Таким образом первый аргумент функции nthcdr
будет результатом
выражения
(% 1 length) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
%
Для того чтобы понять выражение (% 1 length)
, нам надо
познакомиться с функцией %
. Согласно ее документации (которую я
вызвал нажав C-h f % RET) функция %
возвращает
остаток от деления своего первого аргумента на второй. Например остаток
от деления 5 на 2 равен 1. (2 входит в 5 дважды с остатком 1)
Для людей, которые далеки от математики, это может даже казаться удивительным, мы делим меньшее число на большее и получаем остаток. В только что рассмотренном примере мы разделили 5 на 2. А что получится, если мы разделим 2 на 5? Если вы умеете пользоваться дробями ответ ясен 2/5 или .4; но если мы можем использовать только целые числа, результат будет иным. Ясно что 5 может входить в 2 ноль раз, но чему равен остаток? Для этого рассмотрим несколько простых примеров:
Таким образом можно догадаться, что
и так далее.
Так в этом коде, если значение length
равно 5, тогда результат
вычисления
(% 1 5) |
равен 1. (Я все-таки проверил это, расположив курсор после выражения и нажал C-x C-e. В эхо-области появилось 1.)
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
%
в rotate-yank-pointer
Когда kill-ring-yank-pointer
указывает на начало списка удалений
и в функцию rotate-yank-pointer
передана 1, выражение %
возвращает 1:
(- length (length kill-ring-yank-pointer)) => 0 |
следовательно,
(+ arg (- length (length kill-ring-yank-pointer))) => 1 |
и наконец:
(% (+ arg (- length (length kill-ring-yank-pointer))) length) => 1 |
независимо от того чему равно значение length
.
В результате этого выражение setq kill-ring-yank-pointer
можно
привести к виду:
(setq kill-ring-yank-pointer (nthcdr 1 kill-ring)) |
Что здесь происходит легко понять, вместо того чтобы указывать на первый
элемент списка удалений, kill-ring-yank-pointer
теперь будет
указывать на второй элемент.
Ясно, что если в функцию rotate-yank-pointer
вызвали с аргументом
2, тогда kill-ring-yank-pointer
будет равен (nthcdr 2
kill-ring)
; и так далее для различных значений аргумента.
Аналогично, если kill-ring-yank-pointer
вначале указывал на
второй элемент списка удалений, его длина короче чем длина всего списка
удалений на 1, а так как вычисление остатка основано на выражении
(% (+ arg 1) length)
, то kill-ring-yank-pointer
будет
теперь указывать на третий элемент списка удалений, если функцию
rotate-yank-pointer
вызвали со значением 1.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Последний важный вопрос, что произойдет, если
kill-ring-yank-pointer
указывает на последний элемент
списка удалений? Вызов функции rotate-yank-pointer
ни к чему не
приведет? Неправильно! Произойдет очень интересная вещь.
kill-ring-yank-pointer
теперь будет снова указывать на начало
списка удалений.
Давайте попытаемся это понять, анализируя код, если например длина
списка удалений равна 5 и функцию rotate-yank-pointer
вызвали с
аргументом 1. Когда kill-ring-yank-pointer
указывает на
последний элемент списка удалений его длина равна 1. Код выглядит
следующим образом:
(% (+ arg (- length (length kill-ring-yank-pointer))) length) |
Если вместо переменных подставить их числовые значения, выражение будет выглядеть следующим образом:
(% (+ 1 (- 5 1)) 5) |
Это выражение можно вычислить начиная с самого внутреннего списка и
продвигаясь в внешнему: Значение (- 5 1)
равно 4; сумма (+
1 4)
равна 5; и остаток от деления 5 на 5 равен нулю. По этому в теле
функции rotate-yank-pointer
будет вычислено следующее выражение
(setq kill-ring-yank-pointer (nthcdr 0 kill-ring)) |
в результате чего kill-ring-yank-pointer
будет указывать на
начало списка удалений.
Теперь понятно, что последовательные вызовы rotate-yank-pointer
перемещают kill-ring-yank-pointer
от элемента к элементу в списке
удалений до тех пор пока kill-ring-yank-pointer
не достигнет
конца списка; после этого происходит прыжок в начало. Именно поэтому
список удалений называют кольцом (kill ring), так как постоянно
происходит перемещение от конца к началу, как будто у этого списка не
конца! (У кольца нет конца!)
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
yank
После того как мы изучили функцию rotate-yank-pointer
код функции
yank
будет нам легко понятен. В нем только одна сложная часть,
когда происходит обработка аргумента для передачи в функцию
rotate-yank-pointer
.
The code looks like this:
(defun yank (&optional arg) "Вставка последнего удаленного текста. С аргументом C-U, то же самое только точка в начале (а метка в конце). С аргументом n, вставка n-того недавно удаленного текста. Смотри также команду \\[yank-pop]." (interactive "*P") (rotate-yank-pointer (if (listp arg) 0 (if (eq arg '-) -1 (1- arg)))) (push-mark (point)) (insert (car kill-ring-yank-pointer)) (if (consp arg) (exchange-point-and-mark))) |
С первого взгляда мы уже можем понять что происходит в последний строках
функции. Ставится метка; затем вставляется первый элемент (CAR)
kill-ring-yank-pointer
; и после этого, если в функцию передали
как аргумент cons
точка и метка меняются местами, так что курсор
будет в начале вставленного текста, а метка в конце, а не наоборот. Все
это обьясняется в документации к функции. Сама функция интерактивная с
"*P"
. Это означает, что она не работает для буферов
только-для-чтения, и в функцию передается необработанный префикс
аргумент.
Передача аргумента Передаем аргумент в rotate-yank-pointer
.Передаем отрицательный аргумент
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Самая сложная часть функции yank
это там, где происходит
обработка аргумента для передачи в функцию rotate-yank-pointer
.
К счастью эта часть не так сложна, как кажется на первый взгляд.
В результате обработки аргумента одним или двумя выражениями if
получается число, которое и передается в функцию
rotate-yank-pointer
.
Если снабдить код комментариями и отформатировать его для удобочитаемости, то получиться следующее:
(if (listp arg) ; if-часть 0 ; then-часть (if (eq arg '-) ; else-часть, внутреннее if -1 ; внутреннее if's then-часть (1- arg)))) ; внутреннее if's else-часть |
Этот код состоит из двух выражений if
, одна из которых является
else-частью другого.
Первое или внешнее выражение if
проверяет, является ли аргумент
переданный в функцию yank
списком. Кажется немного странным, но
это будет истиной, если функция yank
вызвана без аргумента, так
как тогда аргументу будет присвоено значение nil
, а вычисление
выражения (listp nil)
вернет истину! Так, что, если в функцию
yank
не передали аргумента, функцию rotate-yank-pointer
вызываемую внутри yank
будет передан ноль. Это значит, что
указатель не будет перемещатся и будет вставлен первый элемент на
который указывает kill-ring-yank-pointer
, что мы и ожидали.
Аналогично, если аргумент функции yank
был C-u, это снова
будет считаться списком, и опять при вызове функции
rotate-yank-pointer
ей будет передан ноль. (C-u производит
необработанный префикс-аргумент (4)
, то есть список, состоящий из
одного элемента.) В тоже время, в конце функции этот аргумент будет
считаться как cons
, так что метка будет расположена в конце, а
точка в начале вставленного текста. (Именно для этого и предназначен
аргумент P
к interactive
.)
then-часть внешнего выражения if
обрабатывает случай, когда в
функцию не передан аргумент или когда передано C-u. else-часть
обрабатывает другую ситуацию. else-часть сама это другое выражение
if
.
Внутреннее выражение if
проверяет равен ли аргумент знаку минус.
(Это можно сделать, нажав одновременно клавиши META и - или
нажать клавишу ESC, а потом -). В этом случае в функцию
rotate-yank-pointer
будет передан как аргумент -1. Это
переместит kill-ring-yank-pointer
в обратном направлении, что мы
и хотели.
Если проверка-истина-ложь внутреннего выражения if
вернет ложь
(то есть, если аргумент не знак минус), в этом случае будет вычислятся
else-часть выражения. Это выражение (1- arg)
. Это произойдет
только в том случае, когда аргумент или положительное число или
отрицательное число (а не только знак минус). Выражение (1- arg)
всего лишь уменьшает arg
на единицу и возвращает его. (Функция
-1
) вычитает единицу из своего аргумента.) Это означает, что
если аргумент к функции rotate-yank-pointer
равен 1 он будет
уменьшен до нуля то есть будет вставлен первый элемент на который
указывает kill-ring-yank-pointer
, то есть то что мы и ожидали.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Последний вопрос на который мы еще не ответили, что произойдет если в
функцию остатка %
, или в функцию nthcdr
будет передан
отрицательный аргумент?
Ответ можно найти проведя небольшой эксперимент. После вычисления
(% -1 5)
вернется отрицательное число; и если функцию
nthcdr
вызвать с отрицательным числом она вернет то же самое
значение как будто бы была вызвана с аргументом 0. Это можно проверить
вычислив следующие выражения.
Здесь `=>' указывает на результат вычисления вышестоящего
выражения. Вы может сделать это как обычно расположив курсор после
выражения и нажав C-x C-e (eval-last-sexp
), конечно если вы
читаете этот документ в Инфо не выходя из GNU Emacs.
(% -1 5) => -1 (setq animals '(cats dogs elephants)) => (cats dogs elephants) (nthcdr 1 animals) => (dogs elephants) (nthcdr 0 animals) => (cats dogs elephants) (nthcdr -1 animals) => (cats dogs elephants) |
Так что если в функцию yank
было передано отрицательное число или
знак минус, то указатель kill-ring-yank-point
циклически
перемещается в обратном направлении до конца списка. Затем он остается
там, в отличии от других случаев, когда он перемещается от конца списка
к началу. Это очень разумно.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
yank-pop
После того, как мы изучили функцию yank
понять функцию
yank-pop
станет очень легко. Если пропустить документацию для
сохранения места ее код выглядит следующим образом:
(defun yank-pop (arg) (interactive "*p") (if (not (eq last-command 'yank)) (error "Предыдущая команда не yank")) (setq this-command 'yank) (let ((before (< (point) (mark)))) (delete-region (point) (mark)) (rotate-yank-pointer arg) (set-mark (point)) (insert (car kill-ring-yank-pointer)) (if before (exchange-point-and-mark)))) |
Видно, что это интерактивная функция, аргумент в interactive
равен `p', значит в функцию будет передан обработанный префикс
аргумент. Команду можно использовать только, если предыдущая команда
была yank
; в противном случае печатается сообщение об ошибке.
Эта проверка использует переменную last-command
, которую мы уже
обсуждали. (See section 8.5 copy-region-as-kill
.)
В выражении let
переменной before
присваивается или истина
или ложь в зависимости от того была ли расположена точка перед меткой
или за ней, после этого удаляется область расположенная между точкой и
меткой. Это как раз та область текста, которая была вставлена
предыдущей командой yank и именно этот текст мы сейчас заменим. Затем
указатель kill-ring-yank-pointer
перемещается так чтобы мы заново
не вставили предыдущий вставленный текст. Ставится метка. Затем
вставляется текст на который указывает сейчас
kill-ring-yank-pointer
, это перемещает точку в конец нового
вставленного текста. Если до этого точка была расположена до метки,
тогда точка и метка меняются местами, чтобы все было как раньше. Вот
собственно и все что происходит в этой функции!
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |