[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

B. Обработка списка удалений

Список удалений явля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] [ ? ]

B.1 Функция 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] [ ? ]

B.1.1 Тело функции 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] [ ? ]

else-часть выражения 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] [ ? ]

B.2 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] [ ? ]

B.3 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] [ ? ]

This document was generated on March, 10 2004 using texi2html