[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
Когда вы вырезаете из буфера текст с помощью команды `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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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
требует четырех аргументов:
В нашем случае оказывается, что в zap-to-char
передается
одиночный символ. Из-за некоторых компьютерных тонкостей,
интерпретатор Лиспа обрабатывает одиночный символ по другому, чем он
обрабатывает строку символов. Внутри компьютера, одиночный символ
представлен в другом электронном формате, в отличии от строки
состоящей из одного символа. (В компьютере один символ часто занимает
ровно один байт; но строка может быть короткой или длинной, и
компьютер должен быть готов к этому). Поскольку функция
search-forward
ожидает получить строку, то полученный символ
нужно преобразовать внутри компьютера из одного формата в другой,
иначе функция search-forward
не сможет работать. Для этих целей
используют функцию char-to-string
.
nil
.
nil
. nil
в качестве третьего
аргумента заставит функцию сообщить об ошибке, если поиск окончится
неудачно.
search-forward
--- это число повторов
--- сколько появлений строки искать. Этот аргумент является
необязательным, и если функция вызвана без числа повторов, то этому
аргументу присваивается значение 1. Если этот аргумент отрицательный,
то поиск производится в обратном направлении.
Ниже шаблон для выражения search-forward
:
(search-forward "искомая-строка" границы-поиска что-делать-в-случае-неудачи число-повторов) |
Сейчас давайте взглянем на выражение progn
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
Реализация функции 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
делает следующее: когда поиск производится
вперед (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 ; Вернуть |
Как видно, реализация для Emacs версии 19 делает чуть-чуть меньше чем в Emacs версии 18, но намного проще.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
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] | [ ? ] |
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
.
Fdelete_region
. По принятому соглашению, ее имя начинается с
`F'. Поскольку в С не разрешается использование знак тире в
идентификаторах, то вместо него используется знак подчеркивания.
S
, а не
`F'.
interactive
в функции написанной на Лиспе --- буква за
которой может последовать подсказка. В нашем случае, это буква
"r"
, которая означает, что два аргумента к функции будут
позициями начала и конца области в буфере. В этом коде нет никаких
подсказок.
\n
за которой должно
явно следовать обратный слэш и перевод строки.
Затем идут формальные параметры, с утверждением какого они типа и
затем, как можно вызвать `тело' макроса. Для 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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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] | [ ? ] |
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
является
|
Эта строка не является частью ни внутреннего, ни внешнего выражения
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] | [ ? ] |
Кратко повторим недавно изученные функции.
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
Принимает четыре аргумента:
nil
, или отобразить сообщение об
ошибке.
kill-region
delete-region
copy-region-as-kill
kill-region
вырезает текст между точкой и меткой и сохраняет
его в списке уничтожений, так, что вы может впоследствии вернуть его с
помощью команды yank
.
delete-region
удаляет текст между точкой и меткой из буфера и
отбрасывает его. Вы не сможете вернуть его назад.
copy-region-as-kill
копирует текст между точкой и меткой в
kill-ring
, так, что впоследствии вы можете вернуть его снова.
Эта функция не удаляет и не вырезает текст из буфера.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |
search-forward
;
если вы так сделаете, то вы перепишите существующую в Emacs
функцию. Назовите свою версию как нибудь, например
test-search
).
kill-ring
, если он существует; если в kill-ring
нет
столько элементов, то печатается соответствующее сообщение.
copy-region-as-kill
больше не
устанавливает this-command
. Каковы последствия этого изменения?
Как вы думаете, чем это вызвано?
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |