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

8. Вырезание и сохранение текста

Когда вы вырезаете из буфера текст с помощью команды `kill' в GNU Emacs, то он заноситься в список и вы можете вернуть его обратно с помощью команды `yank'.

(Использование слова `kill' (убить, уничтожить), для операций, которые в особенности ничего не разрушают --- просто историческое недоразумение. Намного более подходящим будет слово `clip' (вырезать, зажать), что именно и делают команды kill --- они вырезают текст из буфера и заносят его в хранилище, откуда его можно вернуть обратно. У меня иногда появляется желание заменить в исходных текстах Emacs все вхождения слова `kill' на `clip', а все слова `killed' на `clipped').

Сохранение текста в списке  
8.1 zap-to-char  Как вырезать текст до символа.
8.2 kill-region  
8.3 delete-region: Погружение в язык Си  Погружение в Си.
8.4 Инициализация переменной с defvar  Как переменной присвоить первоначальное
                                значение.
8.5 copy-region-as-kill  Определение для копирования текста.
8.6 Обзор  
8.7 Упражнения с поиском  

Сохранение текста в списке

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

 
("блок текста" "последний блок")

Для добавления в список текста можно использовать функцию cons, примерно так:

 
(cons "другой блок" 
      '("блок текста" "последний блок"))

Если вы вычислите это выражение, то в эхо-области появится список из трех выражений:

 
("другой блок" "блок текста" "последний блок")

С помощью функций car и nthcdr, вы можете извлечь любой элемент списка, который вы пожелаете. Например, в следующем фрагменте, при выполнении nthcdr 1 ... возвратится список без первого элемента; а car вернет первый элемент от остатка --- то есть второй элемент первоначального списка.

 
(car (nthcdr 1 '("другой блок"
                 "блок текста"
                 "последний блок")))
     => "блок текста"

На самом деле функции в Emacs конечно сложнее чем эти. Функция для вырезания и извлечения текста написана так, что Emacs может вычислить какой элемент списка вы хотите получить --- первый, второй или другой. Кроме этого, когда вы достигните конца списка, то Emacs вернет вам первый элемент списка, а не nil.

Список в котором содержаться блоки текста, называют kill ring. В этой главе мы опишем эту структуру данных и как ее используют, вначале, на примере работы функции zap-to-char. В этой функции вызывается другая функция, которая работает с kill ring. То есть, как обычно, до того как карабкаться на гору, мы заберемся на холм.

В последующей главе описывается как вернуть текст, который мы вырезали из буфера. See section Вставка Текста Обратно.


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

8.1 zap-to-char

Функция zap-to-char реализована по разному в версиях GNU Emacs 18 и 19. В 19 версии функция попроще, и работает немного по другому. Мы вначале изучим функцию написанную для версии 19, а потом для версии 18.

В 19 версии Emacs интерактивная функция zap-to-char удаляет из блока текст между положением курсора (т.е. точкой) до, включительно следующего вхождения описанного символа. Удаленный текст помещается в список уничтожений; и в последствии его можно извлечь оттуда, нажав C-y (yank). Если команде задан аргумент, то вырезается текст до заданного числа символов. То есть, если курсор был в начале предложения и символом был `с', то будет удалено слово `То ес'. Если аргумент был равен двум, то будет удалено `То есть ес' до второго появления заданного символа, включительно, в слове `если'.

В версии 18 Emacs текст удаляется от курсора до символа, но не включая сам символ. То есть в предыдущем примере сам символ `с' не будет удален.

Кроме этого, в версии 18 функция удаляет текст до конца буфера, если заданный символ не найден; а в версии 19, в этом случае отображается только сообщение об ошибке (и ничего не удаляется).

Для того, чтобы определить сколько текста нужно удалить, в обоих версиях zap-to-char используется функция поиска. Различные поисковые функции очень часто используются в функциях работающих с текстом и стоит обратить на них особое внимание, также как и на команду удаления.

Ниже полный текст функции zap-to-char для 19 версии Emacs:

 
(defun zap-to-char (arg char)  ; version 19 implementation
  "Удаляет до и включительно ARG'того появления CHAR
Идет назад если ARG отрицательно; сигнализирует об ошибке если CHAR не
найден"   
  (interactive "*p\ncZap to char: ")
  (kill-region (point)
               (progn
                 (search-forward
                  (char-to-string char) nil nil arg)
                 (point))))

8.1.1 Выражение interactive  
8.1.2 Тело zap-to-char  Краткий обзор.
8.1.3 Функция search-forward  Как найти строку.
8.1.4 Функция progn  
8.1.5 Резюме zap-to-char  Используем point, search-forward.
8.1.6 Реализация для 18 Версии Emacs  Реализация для 18 версии.


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

8.1.1 Выражение interactive

Выражение для interactive в функции zap-to-char выглядит следующим образом:

 
(interactive "*p\ncZap to char: ")

Текст внутри двойных кавычек, "*p\ncZap to char: ", описывает три различные вещи. Первое и самое простое --- звездочка `*', вызовет ошибку если буфер открыт в режиме только-для-чтения. Это означает, что если вы попытаетесь вызвать zap-to-char в буфере который открыт в режиме только-для-чтения, вы не сможете удалить текст, и получите следующее сообщении об ошибке которое гласит "Buffer is read-only"; также ваш терминал может издать звуковой сигнал.

Вторая часть "*p\ncZap to char: " --- это `p'. Эта часть оканчивается символом новой строки, `\n'. `p' означает, что первым аргументом передаваемым функции будет значение `префикс-аргумента'. Префикс-аргумент передается нажатием C-u и затем вводом числа или набором M- и числа. Если вы вызвали функцию интерактивно, без задания префикса, то в функцию будет передано значение 1.

Третья часть "*p\ncZap to char: " --- есть `cZap to char: '. Здесь символ `c' в нижнем регистре означает, что interactive выдаст подсказку и аргумент будет символом. Подсказка идет сразу за `c', и это строка `Zap to char: ' (пробел после двоеточия поставлен для более удобного отображения).

Все это вместе --- это подготовка аргументов для функции zap-to-char, так чтобы они были правильного типа и дает пользователю подсказку.


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

8.1.2 Тело zap-to-char

В теле функции zap-to-char содержатся инструкции которые удаляют текст из буфера от текущего положения курсора до заданного символа. Первая часть выглядит следующим образом:

 
(kill-region (point) ...

(point) --- это текущее положение курсора.

Затем идет выражение progn. Тело выражения progn состоит из вызова search-forward и point.

Легче понять как работает progn после изучения функции search-forward, так что мы сначала рассмотрим search-forward, а затем вернемся к progn.


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

8.1.3 Функция search-forward

Функцию search-forward в zap-to-char используют для нахождения заданного символа. Если поиск закончился успешно, то search-forward оставляет курсор (точку) сразу за последним символом в искомой строке. (В нашем случае искомая строка имеет длину в один символ). Если поиск производился в обратном направлении, то search-forward оставляет точку как раз перед первым символом искомой строки. Также, search-forward возвращает t, если поиск был успешным. (Следовательно перемещение курсора --- это `побочный эффект').

В zap-to-char, функция search-forward выглядит следующим образом:

 
(search-forward (char-to-string char) nil nil arg)

Функция search-forward требует четырех аргументов:

  1. Первый аргумент --- это цель поиска. Это должна быть строка, такая как например, `"z"'.

    В нашем случае оказывается, что в zap-to-char передается одиночный символ. Из-за некоторых компьютерных тонкостей, интерпретатор Лиспа обрабатывает одиночный символ по другому, чем он обрабатывает строку символов. Внутри компьютера, одиночный символ представлен в другом электронном формате, в отличии от строки состоящей из одного символа. (В компьютере один символ часто занимает ровно один байт; но строка может быть короткой или длинной, и компьютер должен быть готов к этому). Поскольку функция search-forward ожидает получить строку, то полученный символ нужно преобразовать внутри компьютера из одного формата в другой, иначе функция search-forward не сможет работать. Для этих целей используют функцию char-to-string.

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

  3. Третий аргумент информирует, что функция должна делать, если поиск окончится неудачей --- сигнализировать об ошибке (и напечатать сообщение) или вернуть nil. nil в качестве третьего аргумента заставит функцию сообщить об ошибке, если поиск окончится неудачно.

  4. Четвертый аргумент search-forward --- это число повторов --- сколько появлений строки искать. Этот аргумент является необязательным, и если функция вызвана без числа повторов, то этому аргументу присваивается значение 1. Если этот аргумент отрицательный, то поиск производится в обратном направлении.

Ниже шаблон для выражения search-forward:

 
(search-forward "искомая-строка"
                границы-поиска
                что-делать-в-случае-неудачи
                число-повторов)

Сейчас давайте взглянем на выражение progn.


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

8.1.4 Функция progn

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

Шаблон для выражения progn очень прост:

 
(progn
  тело...)

В zap-to-char, выражение progn должно сделать две вещи --- расположить курсор на правильную позицию; и вернуть значение точки так, чтобы kill-region знал сколько текста он должен удалить.

Первый аргумент progn --- это search-forward. Когда search-forward найдет строку, функция оставит точку сразу за последним символом искомой строки. (В этом случае искомая строка длинной только в один символ). Если поиск производится в обратном направлении, то search-forward оставит точку сразу перед первым символом искомой строки. Следовательно движение курсора --- побочный эффект.

Второй и последний аргумент progn --- это выражение (point). Это выражение возвращает значение точки которое в нашем случае будет на том месте, где его оставила функция search-forward. Это значение будет возвращено, как значение всего выражения progn, и будет передано как второй аргумент для функции kill-region.


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

8.1.5 Резюме zap-to-char

Теперь, когда мы разобрались как работают функции search-forward и progn, мы может понять как в целом работает функция zap-to-char.

Первый аргумент kill-region --- это позиция курсора, когда была дана команда zap-to-char --- значение точки в это время. Внутри progn, функция для поиска затем перемещает точку как раз за заданный символ, и point возвращает значение точки в этом месте. Функция kill-region получает два значения точки --- первое начало блока текста, а второе --- конец блока, и затем удаляет этот блок.

Функция progn необходима, поскольку kill-region принимает два аргумента; и не будет работать, если выражения search-forward и point записать последовательно, как два аргумента. Выражение progn --- это один аргумент и вернет одно значение. которое необходимо kill-region для своего второго аргумента.


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

8.1.6 Реализация для 18 Версии Emacs

Реализация функции zap-to-char для Emacs версии 18 несколько отличается от реализации для версии 19 --- там удаляется текст до, но не включая заданный символ; и удаляется текст до конца буфера, если заданный символ не найден.

Различие заключается во втором аргументе к команде kill-region. Там: где в 19 версии Emacs функция выглядит следующим образом:

 
(progn
  (search-forward (char-to-string char) nil nil arg)
  (point))

В 18 версии все выглядит по другому:

 
(if (search-forward (char-to-string char) nil t arg)
    (progn (goto-char
            (if (> arg 0) (1- (point)) (1+ (point))))
           (point))
  (if (> arg 0)
      (point-max)
    (point-min)))

Это выглядит значительно сложнее, но все это станет легко понятным, если рассмотреть этот код по частям.

Первая часть это:

 
(if (search-forward (char-to-string char) nil t arg)

Его можно представить с помощью выражения if следующим образом:

 
(if смогли-найти-заданный-символ-и-переместить-туда-курсор
    тогда-уточнить-местоположение-точки-и-вернуть-его
   иначе-переместить-курсор-в-конец-буфера-и-вернуть-значение-точки)

Вычисление выражения if описывает второй аргумент функции kill-region. Поскольку первый аргумент является точкой --- этот процесс позволяет kill-region удалить текст между точкой и заданным символом.

Мы уже упоминали, что побочным эффектом search-forward является перемещение точки. search-forward возвращает t, если поиск завершился успешно, и nil или сообщение об ошибке в зависимости от третьего аргумента с search-forward. В нашем случае третий аргумент равен t и он заставляет функцию вернуть nil, если поиск завершился неудачно. Как мы увидим, довольно легко написать программу реагирующую на тот случай, когда поиск вернет nil.

В реализации zap-to-char для Emacs версии 18, поиск происходит, поскольку if вызывает функцию поиска в выражении для проверка-истинна-ложь. Если поиск успешен, то Emacs вычисляет then-часть выражения if. С другой стороны, если поиск завершился неудачей, тогда Emacs вычисляет else-часть выражения if.

Когда поиск успешен, в выражении if, выполняется выражение progn --- то есть оно запускается, как программа.

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

В этой версии zap-to-char, выражение progn выполняется когда функция search-forward находит искомый символ. Выражение progn должно сделать две вещи --- поместить курсор в точное местоположение; и вернуть это местоположение точки, так чтобы kill-region знал как далеко удалять текст.

Причиной наличия всего этого кода в progn, является то, что когда search-forward находит строку которую она искала, она оставляет точку сразу за последним символом в целевой строке. (В нашем случае целевая строка длинной в один символ). Если поиск производится назад, то search-forward оставляет точку как раз перед первым символом в целевой строке.

Однако эта версия zap-to-char написана так, чтобы не удалять заданный символ. Например, если zap-to-char должна удалить текст до символа `z', то эта версия не удалит сам символ `z'. Поэтому необходимо переместить точку так, чтобы заданный символ не был удален.

Тело выражения progn  


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

Тело выражения progn

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

 
(progn 

  (goto-char                ; Первое выражение в progn.
        (if (> arg 0)       ; Если arg положителен,
            (1- (point))    ;   переместиться назад на один символ;
          (1+ (point))))    ;   иначе переместиться вперед на один символ.
 
  (point))                  ; Второе выражение в progn:
                            ;   вернуть позицию точки.

Выражение progn делает следующее: когда поиск производится вперед (arg положителен), то Emacs оставляет точку как раз за искомым символом. В таком случае необходимо переместить курсор назад на одну позицию. В этом случае выражение в progn будет выглядеть следующим образом: (goto-char (1- (point))). Это выражение перемещает точку на один символ назад. (1- вычитает единицу из своего аргумента, а 1+ наоборот, прибавляет единицу). С другой стороны, если аргумент к zap-to-char отрицателен, то поиск будет произведен в обратном направлении. if определит это и выражение будет выглядеть следующим образом: (goto-char (1+ (point))). (Функция 1+ прибавляет единицу к своему аргументу).

Второй и последний аргумент к prognй --- это выражение (point). Это выражение возвращает значение позиции к которой переместилась точка в первом выражении тела progn. Затем это значение возвращается выражением if и передается как второй аргумент в функцию kill-region.

Короче говоря, функция работает следующим образом: первый аргумент к kill-region --- это позиция курсора в тот момент, когда была запущена команда zap-to-char --- значение точки в это время. Функция поиска затем перемещает точку, если поиск был успешен. Выражение progn затем перемещает точку так, чтобы заданный символ не был удален и возвращает значение точки, после того как это будет выполнено. Функция kill-region после этого удаляет эту область текста.

Наконец, else-часть выражения if выполняется тогда, когда заданный символ не найден. Если аргумент к функции zap-to-char положителен (или он не задан), и заданный символ не найден, то удаляется весь текст от текущего положения курсора до конца доступной области буфера (или до конца буфера если не включено сужение). Если arg отрицателен и заданный символ не найден, то текст удаляется к началу буфера. Это реализуется в следующей инструкции if:

 
(if (> arg 0) (point-max) (point-min))

Здесь говорится, что если arg положительное число, то вернуть значение функции point-max, в противном случае, вернуть значение функции point-min.

Для повторения перепишем инструкции из kill-region, с комментариями:

 
(kill-region
 (point)                    ; начало-области
 (if (search-forward
      (char-to-string char) ; цель-поиска
      nil                   ; границы-поиска: нет
      t                     ; Вернуть nil в случае неудачи.
      arg)                  ; число-повторов.
     (progn                 ; then-часть
       (goto-char     
        (if (> arg 0)
            (1- (point))
          (1+ (point))))
       (point))
   
   (if (> arg 0)            ; else-часть
       (point-max)
     (point-min))))

Как видно, реализация для Emacs версии 19 делает чуть-чуть меньше чем в Emacs версии 18, но намного проще.


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

8.2 kill-region

Функция zap-to-char использует функцию kill-region. Это очень простая функция; без полной строки документации она выглядит следующим образом:

 
(defun kill-region (beg end)
  "Удалить текст между точкой и меткой.
Текст удаляется, но сохраняется в kill ring."
  (interactive "*r")
  (copy-region-as-kill beg end)
  (delete-region beg end))

Главное, что надо отметить --- это то, что в теле используются функции delete-region и copy-region-as-kill, которые мы изучим в последующих разделах.


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

8.3 delete-region: Погружение в язык Си

В функции zap-to-char используется функция kill-region, из которой в свою очередь вызываются две другие функции --- copy-region-as-kill и delete-region. Функцию copy-region-as-kill мы опишем в следующем разделе; она помещает копию блока текста, так, что ее можно вернуть обратно. (See section copy-region-as-kill.)

Функция delete-region удаляет содержимое области текста и вы не сможете вернуть его обратно.

В отличии от многих функций которые обсуждались здесь, delete-region написана не на Emacs Lisp --- она написана на языке С, и это одна из немногих примитивных функций в системе GNU Emacs. Поскольку она очень проста, то я ненадолго вынырну из Лиспа и опишу ее здесь.

Как и многие другие примитивы Emacs, delete-region реализована в форме макроса С, макрос который служит шаблоном для кода. Первый раздел макроса выглядит следующим образом:

 
DEFUN ("delete-region", Fdelete_region, Sdelete_region, 2, 2, "r",
  "Удалить текст между точкой и меткой.\n\
Когда вызывается из программы ожидает два аргумента,\n\
число символов задающие протяженность области которая будет удалена.")

Не объясняя деталей написания макросов, стоит указать, что этот макрос начинается со слова DEFUN. Слово DEFUN выбрано по той причине, что этот код служит таким же целям, что и defun в Лиспе. За словом DEFUN следует семь частей в круглых скобках:

Затем идут формальные параметры, с утверждением какого они типа и затем, как можно вызвать `тело' макроса. Для delete-region `тело' состоит из следующих трех строк:

 
validate_region (&b, &e);
del_range (XINT (b), XINT (e));
return Qnil;

Первая функция, validate_region проверяет правда ли что значения переданные в функцию как начало и конец области текста надлежащего типа и внутри диапазона. Вторая функция, del_range, фактически и производит удаление текста. Если функция завершает свою работу без ошибок, то третья строка вернет Qnil, для успешного завершения.

del_range --- это довольно сложная функция, которую мы не будем здесь рассматривать. Однако все же стоит взглянуть на два аргумента, которые передаются del_range. Это XINT (b) и XINT (e). Когда дело доходит до языка С, b и e --- два тридцати двух-битные целых числа, которые отмечают начало и конец области текста для удаления. Но как и другие числа в Emacs Lisp, из тридцати двух бит для хранения числа используются только 24; оставшиеся восемь бит используются для хранения информации о типе объекта, и некоторых других целях. (На некоторых машинах используются только 6 бит). В нашем случае восемь бит используются для записи того, что эти числа --- позиции в буфере. Когда биты числа используются для таких целей их называют тег. Использование восьми-битовых тегов для каждого тридцати-двух битового целого делает возможным Emacs исполнятся быстрее чем раньше. С другой стороны, поскольку числа ограничены только двадцатью четырьмя битами, то буферы Emacs ограничены приблизительно восемью мегабайтами. (Вы можете резко увеличить максимальный размер буфера изменив определения VALBITS и GCTYPEBITS в `emacs/src/config.h' до компиляции. Смотрите замечание в `emacs/etc/FAQ' который входит в дистрибутив Emacs).

`XINT' --- это С макрос, который извлекает 24 битовое число из тридцати-двух битового объекта Лиспа; восемь бит используемых для других целей отбрасываются. Так del_range (XINT (b), XINT (e)) удаляет область между начальной позицией b и конечной позицией e.

С точки зрения человека который пишет на Лиспе, Emacs выглядит очень простым; но для того чтобы все это работало, внутри него спрятано очень много сложного.


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

8.4 Инициализация переменной с defvar

В отличии от функции delete-region, функция copy-region-as-kill написана на Emacs Lisp. Она копирует область из буфера и сохраняет его в переменной kill-ring. В этом разделе мы рассмотрим как создается и инициализируется эта переменная.

(Снова необходимо отметить, что термин kill-ring является неправильным. Текст, который вырезается из буфера можно вернуть назад --- это не хранилище трупов, а хранилище воскресших из мертвых).

В Emacs Lisp, переменная, такая как kill-ring, создается и инициализируется с помощью особой формы defvar. Это сокращение от "define variable".

defvar --- особая форма похожая на setq в том, что она устанавливает значение переменной. Но она отличается от setq в двух местах: во первых, она устанавливает переменную, только если у нее еще не значения. Если у переменной уже есть значение, то defvar не изменяет его. Во вторых, у defvar может быть строка документации.

Вы можете посмотреть текущее значение любой переменной с помощью функции describe-variable, которая обычно запускается сочетанием клавиш C-h v. Если вы нажмете C-h v, а затем наберете в мини-буфер kill-ring и нажмете RET, то вы увидите, что содержит в данный момент kill ring --- там может быть очень много! И наоборот если вы ничего еще не редактировали в этой сессии Emacs, то kill-ring будет пуст. В конце `*Help*' буфера, вы увидите документацию для kill-ring:

 
Documentation:
List of killed text sequences.

kill-ring определяется с помощью defvar следующим образом:

 
(defvar kill-ring nil
  "List of killed text sequences.")

В этом определении переменной, ей присваивается начальное значение nil, которое весьма разумно, поскольку, если вы ничего не сохраняли, то вы ничего и не получите после команды yank. Строка документации записывается точно также, как и для специальной формы defun. Точно также первая строка должна быть завершенным предложением, поскольку некоторые команды, например apropos, отображают только первую строку документации. Последующие строки не надо выравнивать; в противном случае они будут выглядеть странно когда вы используете C-h v (describe-variable).

Большая часть переменных являются внутренними для Emacs, но некоторые используются как опции так что вы легко можете установить их с помощью команды edit-options. Эти установки действительны только во время текущей сессии редактирования; чтобы установить переменную постоянно ее надо записать в ваш `.emacs'. See section Ваш файл `.emacs'.)

Опции предназначенные для установки пользователями можно отличить от других переменных по звездочке `*', в первом столбце их строки документации.

Например:

 
(defvar line-number-mode nil
  "*Не-nil означает отображать номер текущей строки в строке состояния.")

Это означает, что вы можете использовать команду edit-options для того чтобы изменить значение переменной line-number-mode.

Конечно, вы можете также изменить значение переменной line-number-mode вычислив следующее выражение setq:

 
(setq line-number-mode t)

See section Используя setq.


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

8.5 copy-region-as-kill

Функция copy-region-as-kill копирует область текста из буфера и сохраняет ее в переменной, которая называется kill-ring.

Если вы вызовете copy-region-as-kill сразу после команды kill-region, то Emacs добавит свежескопированный текст к предыдущему тексту. Это означает, что если ли вы с помощью команды yank вставите текст обратно, то вы получите все сразу --- и последний и предпоследний удаленный текст. С другой стороны, если перед командой copy-region-as-kill была выполнена какая-либо другая команда, то текст будет сохранен, как отдельный элемент в список уничтожений.

Ниже приведена полная версия функции copy-region-as-kill, отформатированная для ясности и с добавленными комментариями:

 
(defun copy-region-as-kill (beg end)
  "Сохранить удаленную область в kill-ring."
  (interactive "r")

  (if (eq last-command 'kill-region)

      ;; then-часть: Добавить ново удаленный текст
      ;;   к предыдущему удаленному тексту.
      (kill-append (buffer-substring beg end) (< end beg))

    ;; else-часть: Добавить ново удаленный текст как отдельный элемент
    ;;   к kill ring и укоротить его если необходимо.
    (setq kill-ring
          (cons (buffer-substring beg end) kill-ring))
    (if (> (length kill-ring) kill-ring-max) 
        (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))

  (setq this-command 'kill-region)
  (setq kill-ring-yank-pointer kill-ring))

Как обычно, эту функцию можно разделить на составные части:

 
(defun copy-region-as-kill (argument-list)
  "documentation..."
  (interactive "r")
  body...)

Аргументы нызваютсяя beg и end и функция интерактивная с аргументом к interactive "r", так что два аргумента должны являться началом и концом области. Если вы читаете этот документ с самого начала, то все это стало для вас тривиальным.

Документация может показаться не совсем верной если вы не помните что слово `kill' здесь имеет немного другое значение.

Тело функции начинается с оператора if. Он предназначен для двух различных ситуаций --- исполняется ли эта команда сразу за командой kill-region или нет. В первом случае, новый блок текста добавляется к предыдущему удаленному тексту. В противном случае он вставляется в начало kill-ring, как отдельный элемент.

Последние две строки функции --- это два выражение setq. Одно из них присваивает переменной this-command значение kill-region, а другое заставляет переменную kill-ring-yank-pointer указывать на kill-ring.

Тело copy-region-as-kill заслуживает более детального обсуждения.

8.5.1 Тело copy-region-as-kill  


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

8.5.1 Тело copy-region-as-kill

Функция copy-region-as-kill написана так, чтобы два и более удаления комбинировали текст в один кусок. Более того, удаления, которые удаляют вперед от текущей позиции курсора добавляются в конец предыдущего удаленного текста, а команды, которые удаляют текст назад добавляются в начало предыдущего удаленного текста. Таким образом слова всегда остаются в правильном порядке.

В этой функции используются две переменные, которые запоминают предыдущую и текущую команду Emacs. Это переменные this-command и last-command.

Обычно, когда выполняется какая-нибудь функция, Emacs устанавливает значение переменной this-command равной имени выполняемой функции (в нашем случае это должно быть copy-region-as-kill). Одновременно Emacs устанавливает значение last-command равным предыдущему значению this-command. Однако команда copy-region-as-kill ведет себя немного по-другому; она устанавливает переменную this-command равную kill-region, что является именем функции из которой мы вызвали copy-region-as-kill.

В первой части тела функции copy-region-as-kill, выражение if определяет равно ли значение last-command kill-region. Если да, то вычисляется then-часть выражения if; в ней используется функция kill-append чтобы слить удаленный текст с текстом который является первым элементом (CAR) в kill-ring. С другой стороны, если значение last-command не равно kill-region, то функция copy-region-as-kill добавляет новый элемент к kill-ring.

Выражение if читается следующим образом; в нем используется eq, функция которую мы еще не видели:

 
(if (eq last-command 'kill-region)
    ;; then-часть
    (kill-append (buffer-substring beg end) (< end beg))

Функция eq проверяет действительно ли ее первый аргумент тот же самый объект Лиспа, как и второй ее аргумент. Функция eq похожа на функцию equal тем, что она используется в тестах на равенство, но она отличается тем, что пытается определить представляют ли два символа действительно один и тот же объект внутри компьютера, но под разными именами. А функция equal определяет правда ли структура и содержание двух выражений одинаково.

Функция kill-append  
Else-часть copy-region-as-kill  


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

Функция kill-append

Функция kill-append выглядит следующим образом:

 
(defun kill-append (string before-p)
  (setcar kill-ring
          (if before-p
              (concat string (car kill-ring))
              (concat (car kill-ring) string))))

Мы можем рассмотреть эту функцию по частям. Здесь функция setcar использует concat для того, чтобы присоединить новый текст к car kill-ring. Добавлять текст, как отдельный элемент или присоединять его к предыдущему тексту --- это зависит от результатов выражения if:

 
(if before-p                            ; if-часть
    (concat string (car kill-ring))     ; then-часть
  (concat (car kill-ring) string))      ; else-часть

Если блок текста был удален перед блоком текста, удаленным прошлой командой, то он будет добавлен перед текстом, который был сохранен предыдущей командой удаления текста; и наоборот, если удаленный текст следовал за ним, то его добавят после предыдущего текста. Выражение if зависит от предиката before-p, который необходим для того, чтобы принять решение о том, добавлять ли свеже-скопированный текст перед, или после предыдущего скопированного текста.

Символ before-p --- это имя одного из аргументов к kill-append. Когда вычисляется функция kill-append этот аргумент связывается со значением, которое возвращает вычисление фактического аргумента. В данном случае это выражение (< end beg). Само это выражение не определяет, дейстивтельно ли что удаленный текст в этой команде размещался перед, или после удаленного текста; все что она делает --- это определяет действительно ли переменная end меньше, чем переменная beg. Если это так, то это означает, что пользователь наверняка движется в направлении начала буфера. Также результат вычисления предикативного выражения (< end beg) будет истинным и текст будет добавлен перед предыдущим текстом. С другой стороны, если значение переменной end больше, чем значение переменной beg, то текст будет добавлен после предыдущего текста.

Когда свеже-сохраненный текст будет добавлен, то строка с новым текстом будет слита со старым перед ним:

 
(concat string (car kill-ring))

Но если текст будет добавлен, то она будет слита с текстом, но после старого текста:

 
(concat (car kill-ring) string))

Чтобы понять, как это работает, нам вначале надо изучить функцию concat. Эта функция соединяет вместе две строки. Результатом является строка. Например:

 
(concat "abc" "def")
     => "abcdef"

(concat "новый " 
        (car '("первый элемент" "второй элемент")))
     => "новый первый элемент"

(concat (car 
        '("первый элемент" "второй элемент")) " модифицирован")
     => "первый первый элемент"

Теперь имеет смысл рассмотреть функцию kill-append --- она изменяет содержимое буфера уничтожений. Список уничтожений (kill ring) --- это список, каждый из элементов которого является сохраненным текстом. Функция setcar в действительности изменяет первый элемент данного списка. Она делает это используя функцию concat для замены предыдущего первого элемента списка уничтожений (поле CAR списка уничтожений) на новый элемент, созданный объединением старого и нового текста. Новый сохраняемый текст помещается перед, или после старого сохраненного текста, в зависимости от того, где находился этот текст по отношению к предыдущему вырезанному фрагменту. Все объединение становится первым элементом списка уничтожений.

В данном случае, вот как выглядит начало моего списка уничтожений:

 
("связываем вместе" "сохраненный текст" "элемент" ...


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

Else-часть copy-region-as-kill

Теперь вернемся назад к объяснению copy-region-as-kill:

Если последняя команда была не kill-region, то вместо вызова функции kill-append, происходит вызов else-части следующего кода:

 
(if true-or-false-test
    what-is-done-if-test-returns-true
  ;; else-часть
  (setq kill-ring
        (cons (buffer-substring beg end) kill-ring))
  (if (> (length kill-ring) kill-ring-max)
      (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))

Строка с setq в else-части, устанавливает новое значение списка уничтожений равной результату добавления удаляемой строки к старому списку уничтожений.

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

 
(setq example-list '("вот предложениме" "другое предложение"))

После оценки этого выражения с помощью C-x C-e, вы можете оценить example-list и увидите, что возвращается следующее:

 
example-list
     => ("вот предложениме" "другое предложение")

Теперь, вы можете добавить новый элемент в данный список, путем оценки следующего выражения:

 
(setq example-list (cons "третье предложение" example-list))

Когда мы оценим example-list, мы обнаружим, что его значение равно:

 
example-list
     => (третье предложение" "вот предложениме" "другое предложение")

Таким образом, третий элемент был добавлен в список с помощью функции cons.

Это аналогично тому, что делают setq и cons в функции, за тем исключением, что для получения части текста буфера и передачи его функции cons, используется функция buffer-substring. Вот эта строка:

 
(setq kill-ring (cons (buffer-substring beg end) kill-ring))

Следующий кусок else-части copy-region-as-kill --- это другое выражение if. Это выражение if сохраняет список уничтожений от излишнего разрастания. Этот код выглядит следующим образом:

 
(if (> (length kill-ring) kill-ring-max)
    (setcdr (nthcdr (1- kill-ring-max) kill-ring) nil)))

Этот код проверяет, не превысила ли длина списка уничтожений некоторого максимального значения. Это значение kill-ring-max (которое по умолчанию равно 30). Если длина списка уничтожений слишком большая, то этот код устанавливает последний элемент списка уничтожений равным nil. Это делается с помощью двух функций --- nthcdr и setcdr.

Мы выше рассматривали функцию setcdr (see section setcdr). Она устанавливает значение поля CDR списка, точно также, как и setcar устанавливает значение поля CAR списка. Однако в этом случа, setcdr не установит значение поля cdr всего списка уничтожений; функция nthcdr используется для того, чтобы установить cdr предпоследнего элемента списка уничтожений --- это означает, что поскольку cdr предпоследнего элемента является последним элементом списка уничтожений, то будет установлено значение последнего элемента списка уничтожений.

Функция nthcdr работает путем повторяющегося взятия поля CDR списка --- она берет CDR от CDR от CDR ... Она делает это N раз, и возвращает результат.

Таким образом, если у нас есть четырех-элементный список, который должен стать трех-элементным, то мы должны установить поле CDR предпоследнего элемента равным nil, и таким образом сократить список.

Вы можете увидеть это путем последовательной оценки следующих трех выражений. Сначала, установите значение trees равным (maple oak pine birch), затем установите значение поля CDR второго поля CDR, равным nil и затем посмотрите на значение trees:

 
(setq trees '(maple oak pine birch))
     => (maple oak pine birch)

(setcdr (nthcdr 2 trees) nil)
     => nil

trees
     => (maple oak pine)

(Значение, возвращенное выражением setcdr равно nil, поскольку это то значение, которое получило поле CDR).

Для повторения, в copy-region-as-kill, функция nthcdr берет CDR на один меньшее число раз, чем максимально разрешенная длина списка уничтожений, и поле CDR этого элемента (который является оставшимися элементами списка уничтожений) делается равным nil. Это предотвращает список уничтожений от излишнего разрастания.

Предпоследней строкой функции copy-region-as-kill является

 
(setq this-command 'kill-region)

Эта строка не является частью ни внутреннего, ни внешнего выражения if, так что она оценивается при каждом вызове copy-region-as-kill. Здесь вы найдем место, где this-command получает значение kill-region. Как мы увидели ранее, когда выполняется следующая команда, то last-command получит это значение.

В заключение, последняя строка copy-region-as-kill выглядит так:

 
(setq kill-ring-yank-pointer kill-ring)

kill-ring-yank-pointer --- это глобальная переменная, которая получает устанавливается в kill-ring.

Даже хотя kill-ring-yank-pointer и называется `pointer' (указатель), это переменная, аналогичная списку уничтожений. Однако, ее имя было выбрано таким образом, чтобы помочь людям понять как используется эта переменная. Переменная используется в функциях, таких как yank и yank-pop (see section Вставка Текста Обратно).

Это ведет нас к коду, который вставляет обратно текст, который был выкушен из буфера --- к командам вставки (yank). Однако, до обсуждения команд вставки, лучше изучить как списки реализованы внутри компьютера. Это сделает более простым такие загадки, как использование термина `указатель (pointer)'.


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

8.6 Обзор

Кратко повторим недавно изученные функции.

car
cdr
car возвращает первый элемент списка; cdr возвращает второй, и последующие элементы списка.

Например:

 
(car '(1 2 3 4 5 6 7))
     => 1
(cdr '(1 2 3 4 5 6 7))
     => (2 3 4 5 6 7)

cons
cons конструирует список, добавляя свой первый аргумент ко второму аргументу.

Например:

 
(cons 1 '(2 3 4))
     => (1 2 3 4)

nthcdr
Возвращает результат вызова cdr `n' раз на списке. То есть `остаток остатка`.

Например:

 
(nthcdr 3 '(1 2 3 4 5 6 7))
     => (4 5 6 7)

setcar
setcdr
setcar изменяет первый элемент списка; setcdr изменяет второй, и последующий элементы списка.

Например:

 
(setq triple '(1 2 3))

(setcar triple '37)

triple
     => (37 2 3)

(setcdr triple '("foo" "bar"))

triple
     => (37 "foo" "bar")

progn
Последовательно вычисляет каждый аргумент и затем возвращает значение последнего из них.

Например:

 
(progn 1 2 3 4)
     => 4

save-restriction
Запоминает включено ли сужение в текущем буфере, и если включено, то после вычисления своих аргументов восстанавливает его.

search-forward
Ищет строку, и если строка найдена, то перемещает точку.

Принимает четыре аргумента:

  1. Искомую строку.

  2. Границу поиска, необязательный параметр.

  3. Необязательный параметр --- что делать, если поиск окончится неудачей --- вернуть nil, или отобразить сообщение об ошибке.

  4. Необязательный параметр --- сколько раз повторять поиск; если аргумент отрицательный, то поиск осуществляется в обратном направлении.

kill-region
delete-region
copy-region-as-kill

kill-region вырезает текст между точкой и меткой и сохраняет его в списке уничтожений, так, что вы может впоследствии вернуть его с помощью команды yank.

delete-region удаляет текст между точкой и меткой из буфера и отбрасывает его. Вы не сможете вернуть его назад.

copy-region-as-kill копирует текст между точкой и меткой в kill-ring, так, что впоследствии вы можете вернуть его снова. Эта функция не удаляет и не вырезает текст из буфера.


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

8.7 Упражнения с поиском


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

This document was generated on March, 10 2004 using texi2html