РУКОВОДСТВО ПОЛЬЗОВАТЕЛЯ VIM - Брам Мооленаар

Написание сценариев Vim

Язык сценариев Vim используется в файле инициализации vimrc, в правилах синтаксиса и во многих других местах. В этой главе объясняются детали использования этого языка. Это будет длинная глава, поскольку язык сценариев Vim имеет огромные возможности.

41.1    Введение
41.2    Переменные
41.3    Выражения
41.4    Условия
41.5    Выполнение выражений
41.6    Использование функций
41.7    Определение функции
41.8    Обработка исключительных ситуаций
41.9    Ряд замечаний
41.10    Написание модулей
41.11    Написание модуля типа файла
41.12    Написание модуля для компилятора

Следующая глава: Добавление новых меню
Предыдущая глава: Создание новых команд
Содержание: Руководство пользователя Vim


41.1 Введение

Впервые со сценариями Vim вы сталкиваетесь в файле vimrc. Vim читает этот файл при запуске и выполняет содержащиеся там команды. С их помощью можно, к  примеру, устанавливать желаемые значения опций. Вообще, в этом файле можно использовать любые команды, начинающиеся с двоеточия, ещё называемые командами Ex или командами командной строки.

Сценариями Vim являются также и файлы с описаниями правил синтаксиса и файлы, которые устанавливают значения опций для определённых типов файлов.  В отдельном файле сценария можно также описывать какие-либо сложные макросы. Вы сами можете придумать какое нибудь еще полезное применение для сценариев Vim.

Начнём с простого примера:

:let i = 1
:while i < 5
:  echo "счётчик равен" i
:  let i = i + 1
:endwhile

Замечание: В файле сценария символ ":" не требуется. Он используется обычно для перехода в командную строку в самом редакторе, а в сценариях можно вполне обходиться и без него. Тем не менее, мы будем пользоваться этим символом здесь, чтобы было понятно, что речь идёт о командах Ex, а не командах Обычного режима.

Команда ":let" присваивает значение переменной. Синтаксис этой команды такой:

:let {переменная} = {выражение}

В данном случае именем переменной является "i", а выражением -- простое значение, 1.

Команда ":while" открывает цикл. Синтаксис этой команды такой:

:while {условие}
:  {выражения}
:endwhile

Выражения, указанные до соответствующего ":endwhile" выполняются до тех пор, пока условие является истиной. В нашем примере в качестве условия используется выражение "i < 5". Оно является истиной до тех пор, пока значение переменной i меньше 5.

Команда ":echo" выводит свои аргументы в строке сообщения. В нашем случае аргументами этой команды являются строка "счётчик равен" и значение переменной i. Поскольку i равняется единице, то будет выведено сообщение:

счётчик равен 1 ~

Затем следует ещё одна команда ":let i =". В качестве значения используется выражение "i + 1". К значению переменной i прибавляется 1 и полученный
результат присваивается переменной i.

Итак, вывод нашего сценария:

счётчик равен 1 ~
счётчик равен 2 ~
счётчик равен 3 ~
счётчик равен 4 ~

Замечание: Если ваш цикл выполняется бесконечно, то можно остановить его при помощи команды CTRL-C (CTRL-Break на MS-Windows).

ТРИ ВИДА ЧИСЕЛ

Числа могут быть десятичные, шестнадцатеричные и восьмеричные. Шестнадцатеричное число начинается с "0x" или "0X". Например, "0x1f" это число 31. Восьмеричное число начинается с нуля. "017" это 15. Будьте внимательны: если вы поместите 0 перед десятичным числом, то оно будет восприниматься как восьмеричное!

Команда ":echo" всегда выводит десятичные числа. Например:

     :echo 0x7f 036
< 127 30 ~

Число может быть отрицательным, если указан знак минус. Это работает также с шестнадцатеричными и восьмеричными числами. Кроме того, знак минус используется для операции вычитания. Сравните с предыдущим примером:

     :echo 0x7f -036
<    97 ~

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

:echo 0x7f - 036

41.2 Переменные

Имя переменной может состоять из символов ASCII, цифр и символа подчёркивания. Имя не может начинаться с цифры. Вот примеры допустимых имён:

counter
_aap3
very_long_variable_name_with_underscores
FuncLength
LENGTH

Имена "foo+bar" и "6var" недопустимы.

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

:let

Глобальные переменные можно использовать где угодно. Это означает, что если переменная "count" используется в одном файле сценария, то она также может быть использована и в другом. Это приводит по крайней мере к путанице, но может привести и к серьёзным проблемам. Поэтому, переменные можно сделать также локальными, относящимися только к текущему файлу сценария, путём добавления "s:". Например, представим, что один из сценариев содержит такой код:

:let s:count = 1
:while s:count < 5
:  source other.vim
:  let s:count = s:count + 1
:endwhile

Поскольку "s:count" является локальной переменной, то вы можете быть уверенны, что считывание какого-нибудь другого файла сценария "other.vim" никак не повлияет на значение этой переменной. Если в "other.vim" тоже используется переменная "s:count", то это будет уже совсем другая переменная, локальная по отношению к своему сценарию. Подробнее о локальных переменных сценария см. здесь: |переменные_сценариев|.

Существуют также и другие виды переменных, см. |внутренние_переменные|. Наиболее часто употребляются следующие:

b:name локальная по отношению к буферу переменная
w:name локальная по отношению к окну переменная
g:name глобальная переменная (в том числе внутри функции)
v:name переменная, предопределённая Vim
УДАЛЕНИЕ ПЕРЕМЕННЫХ

Переменные занимают память и появляются в выводе команды ":let". Чтобы удалить переменную, используйте команду ":unlet". Например:

:unlet s:count

Эта команда удаляет локальную для сценария переменную "s:count" и освобождает занимаемую память. Если вы не уверены, существует ли такая переменная, и не хотите получать сообщение об ошибке в том случае, если такая переменная не существует, то добавьте к команде символ !:

:unlet! s:count

После завершения сценария использованная локальная переменная не будет удаляться автоматически. При следующем исполнении сценария будет использоваться старое значение. Например:

:if !exists("s:call_count")
:  let s:call_count = 0
:endif
:let s:call_count = s:call_count + 1
:echo "вызов" s:call_count "раз"

Функция "exists()" проверяет, была ли данная переменная уже определена раннее. Аргументом этой функции служит имя переменной, существование которой надо проверить. Обратите внимание -- имя переменной, а не сама переменная! Если вы вместо этого напишите:

:if !exists(s:call_count)

то в качестве имени переменной будет использоваться значение переменной s:call_count. Это совсем не то, что вы хотели.

Восклицательный знак ! изменяет значение на обратное. Если значение является истиной, то оно становится ложью. Если значение ложь, то оно становится истиной. Вы можете читать этот символ как слово "не". Иными словами, смысл выражения "if !exists()" можно понять как "если не exists()".

Vim называет истиной всё, что отличается от 0. 0 является ложью.

СТРОКОВЫЕ ПЕРЕМЕННЫЕ И КОНСТАНТЫ

До сих пор мы использовали в качестве значения переменной только числа. Однако, можно использовать также и строки. Vim поддерживает только два типа переменных: строки и числа. Тип определяется динамически, иными словами он устанавливается всякий раз, когда выполняется команда присваивания ":let".

Чтобы присвоить строковое значение переменной, вам необходимо использовать строковую константу. Строковые константы бывают двух видов. Во-первых, это строки в дойных кавычках:

     :let name = "петя"
     :echo name
<    петя ~

Если вы хотите поместить двойную кавычку внутри строки, то её необходимо экранировать при помощи символа обратной косой черты:

     :let name = "\"петя\""
     :echo name
<    "петя" ~

Чтобы избежать необходимости использовать обратную косую черту, можно также пользоваться строками в одинарных кавычках:

     :let name = '"петя"'
    :echo name
<    "петя" ~

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

В строках в двойных кавычках можно использовать различные специальные символы. Вот несколько наиболее полезных:

\t <Tab>
\n <NL>, перенос строки
\r <CR>, <Enter>
\e <Esc>
\b <BS>, удаление символа слева от курсора
\" "
\\ \, обратная косая черта
\<Esc> <Esc>
\<C-W> CTRL-W

Последние два примера показывают, как можно использовать форму "\<имя>" для включения в строку специальной кнопки "имя".

Полный список специальных символов в строках см. в |выражение-кавычки|.


41.3 Выражения

В Vim используется простой, и в то же время богатый по возможностям, способ работы с выражениями. Определение этого способа можно прочитать здесь: |выражение-синтаксис|. Остановимся здесь на самых важных моментах.

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

$ИМЯ переменная окружения
&имя опция
@r регистр

В качестве примера:

:echo "Значение опции 'tabstop':" &ts
:echo "Ваш домашний каталог:" $HOME
:if @a > 5

Форма &имя может также быть использована для сохранения значения опции, установки нового значение и восстановления старого значения после выполнения ряда операций. Пример:

:let save_ic = &ic
:set noic
:/Начало/,$delete
:let &ic = save_ic

Это позволяет убедиться, что поиск по шаблону "Начало" применяется в регистронезависимом режиме, с выключенной опцией 'ignorecase'. И в то же время, после выполнения команды поиска восстанавливается прежнее значение этой опции.

АРИФМЕТИКА

Описанные элементы можно сочетать, и тогда это становится действительно интересно! Попробуем для начала заняться математикой с числами:

a + b сложение
a - b вычитание
a * b умножение
a / b деление
a % b остаток

Используется обычный приоритет операций. Пример:

     :echo 10 + 5 * 2
<    20 ~

Элементы можно группировать при помощи скобок. Тоже никаких сюрпризов. Пример:

     :echo (10 + 5) * 2
<    30 ~

Строки можно соединять с помощью ".". Пример:

     :echo "лом" . "бард"
<    ломбард ~

Если команда ":echo" используется с несколькими аргументами, то они разделяются при выводе пробелом. А в только что приведённом примере всего один аргумент, выражение, поэтому пробел не используется.

Из языка C заимствовано условное выражение:

a ? b : c

Если "a" является истиной, то значением выражения является "b", в ином случае значением является "c" Пример:

     :let i = 4
    :echo i > 5 ? "i большое" : "i маленькое"
<    i маленькое ~

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

(a) ? (b) : (c)

41.4 Условия

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

:if {условие}
   {выражения}
:endif

{выражения} будут выполняться только в том случае, когда значением {условия} является истина (не-ноль). Выражения, конечно, должны быть допустимыми командами. Если в них содержится мусор, то Vim не найдёт соответствующего ":endif".

Вы можете также использовать ":else". В общем виде:

:if {условие}
   {выражения}
:else
   {выражения}
:endif

{выражения} после :else будут выполняться только в том случае, если не выполняются выражения после :if.

Кроме того, также есть ещё команда ":elseif":

:if {условие}
   {выражения}
:elseif {условие}
   {выражения}
:endif

Принцип работы точно такой же, как если бы мы использовали ":else" и затем ":if", но в этом случае не требуется использование дополнительного ":endif".  

Полезный пример для файла vimrc -- проверка значения опции 'term' и выполнение операций в зависимости от этого значения:

:if &term == "xterm"
:  " Выполняем команды для xterm
:elseif &term == "vt100"
:  " Выполняем команды для терминала vt100
:else
:  " Выполняем команды для других терминалов
:endif
ЛОГИЧЕСКИЕ ОПЕРАЦИИ

Мы уже использовали некоторые логические операции. Вот наиболее полезные из них:

a == b равно
a != b не равно
a >  b больше чем
a >= b больше или равно
a <  b меньше чем
a <= b меньше или равно

Если условие выполняется, то результат равен 1, иначе результат равен 0. Пример:

:if v:version >= 600
:  echo "примите поздравления"
:else
:  echo "у вас слишком старая версия, обновите программу!"
:endif

В данном случае используется предопределённая Vim переменная, значение которой равно версии Vim. В версии 6.0 значение переменной v:version равно 600, в версии 6.1 -- 601. Это полезно при написании сценария, который работает с различными версиями Vim. |v:version|

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

При сравнении строки с числом, строка прежде всего превращается в число. В том случае, если строка не напоминает число, её значением является 0. Пример:

:if 0 == "один"
:  echo "да"
:endif

Этот сценарий выведет "да", поскольку "один" не похоже на число и будет преобразовано в число 0.

Для строк возможны еще две операции:

a =~ b совпадает с
a !~ b не совпадает с                    

Левая часть, "a", используется как строка. Правая часть, "b", используется как шаблон, такой же шаблон, как и для команды поиска. Пример:

:if str =~ " "
:  echo "в str встречается пробел"
:endif
:if str !~ '\.$'
:  echo "str не заканчивается точкой"
:endif

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

При сравнении строк используется опция 'ignorecase'. Если вас это не устраивает, добавьте "#" для учёта регистра символов и "?" для игнорирования регистра. Таким образом, "==?" сравнивает две строки без учёта регистра, а "!~#" проверяет строку на отсутствие соответствия шаблону, принимая во внимание регистр символов. Полную таблицу возможных комбинаций см. в |выражение-==|.

ЕЩЁ О ЦИКЛАХ

Команда ":while" уже упоминалась. Между ":while" и ":endwhile" могут быть использованы ещё два выражения:

:continue Возвращает управление к началу цикла while; цикл продолжается
:break Переходит к ":endwhile" и прекращает цикл

Пример:

:while counter < 40
:  call do_something()
:  if skip_flag
:    continue
:  endif
:  if finished_flag
:    break
:  endif
:  sleep 50m
:endwhile

Команда ":sleep" устраивает Vim передышку. Значение "50m" указывает на 50 миллисекунд, а ":sleep 4" вызывает паузу в 4 секунды.


41.5 Выполнение выражений

До сих пор команды в сценарии выполнялись Vim напрямую. Команда ":execute" представляет очень мощное средство для создания и выполнения команд: она позволяет выполнять результат выражения.

Например, если мы хотим прыгнуть к метке, заключённой в переменной:

:execute "tag " . tag_name

Мы используем "." для соединения строки "tag " со значением переменной "tag_name". Если "tag_name" содержит величину "get_cmd", то будет выполнена команда:

:tag get_cmd

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

:normal gg=G

Эта команда переносит курсор в первую строку и форматирует все строки в тексте при помощи оператора "=".

Чтобы ":normal" можно было выполнять с выражениями, используйте её комбинацию с командой ":execute"":execute". Пример:

:execute "normal " . normal_commands

В переменной "normal_commands" должны содержаться команды Обычного режима

Убедитесь, что аргумент ":normal" -- полностью законченная команда. Иначе Vim дойдёт до конца аргумента и прекратит выполнение. В частности, если вы переходите в режим Вставки, то не забудьте вернуться в Обычный режим. Например:

:execute "normal Iновый текст \<Esc>"

Эта команда позволяет вставить слова "новый текст " в текущей строке. Обратите внимание на использование символов "\<Esc>" для передачи нажатия на спецкнопку. Это позволяет избежать непосредственного нажатия на кнопку <Esc> во время набора команды сценария.

 


41.6 Использование функций

 

Vim определяет множество встроенных функций и так существенно расширяет свою функциональность. В этом разделе будет дано несколько примеров. Полный список можно посмотреть здесь: |функции|.

Функция вызывается с помощью команды ":call". Параметры функции передаются внутри скобок, разделённые запятыми. Например:

:call search("Дата: ", "W")

Эта команда вызывает функцию search(), с аргументами "Дата: " и "W". Первый аргумент функции search() используется в качестве шаблона для поиска, а второй аргумент является флагами поиска. Флаг "W" указывает, что поиск не переходит к началу файла после достижения конца.

Функцию можно вызывать из выражения. Пример:

:let line = getline(".")
:let repl = substitute(line, '\a', "*", "g")
:call setline(".", repl)

Здесь функция getline() получает строку из текущего файла. Её аргументом является указание на номер строки. В данном случае используется ".", т.е. строка, в которой находится курсор.

Функция substitute() похожа по действию на команду ":substitute". Первым аргументом является строка, в которой выполняется замена. Вторым аргументом является шаблон для замены, третьим -- строка для замены, а последним - флаги.

Функция setline() устанавливает строку, на которую указывает первый аргумент, в значение, указанное вторым аргументом. В этом примере строка под курсором заменяется на результат выполнения функции substitute(). Таким образом, наш сценарий делает то же самое, что и команда:

:substitute/\a/*/g

Использование функций становится более интересным, если до и после вызова substitute() происходит дополнительная обработка.

ФУНКЦИИ

Существует множество функций. Мы приводим здесь список, сгруппированный по их назначению. Алфавитный список можно посмотреть здесь: |функции|. Используйте CTRL-] на имени функции для получения подробной справки.

Работа со строками:
char2nr() получить значение ASCII символа
nr2char() получить символ по значению ASCII
escape() экранировать символы в строке с помощью '\'
strtrans() преобразовать строку в печатный вид
tolower() преобразовать символы в строке в строчные
toupper() преобразовать символы в строке в прописные
match() позиция начала соответствия шаблону в строке
matchend() позиция конца соответствия шаблону в строке
matchstr() соответствие шаблону в строке
stridx() первое соответствие короткой строке в длинной
strridx() последнее соответствие короткой строке в длинной
strlen() длинна строки
substitute() заменить соответствия шаблону на строку
submatch() найти соответствие с ":substitute"
strpart() получить часть строки
expand() заменить специальные символы на их значение
type() тип переменной
iconv() преобразование текста из одной кодировки в другую
Работа с текстом в текущем буфере:
byte2line() получить номер строки по номеру байта
line2byte() номер байта в определённой строке
col() номер колонки для курсора или отметки
virtcol() номер колонки экрана для курсора или отметки
line() номер строки для курсора или отметки
wincol() номер колонки окна для курсора
winline() номер строки окна для курсора
cursor() поместить курсор в заданную строку/колонку
getline() получить строку из буфера
setline() заменить строку в буфере
append() добавить {строку} под строкой с {номером}
indent() отступ для определённой строки
cindent() отступ в соответствии с правилами C
lispindent() отступ в соответствии с правилами Lisp
nextnonblank() найти следующую непустую строку
prevnonblank() найти предыдущую непустую строку
search() найти соответствие шаблону
searchpair() найти другой конец пары
Системный функции и работа с файлами:
browse() показать диалог для выбора файла
glob() подстановка масок в шаблонах имён файлов
globpath() подстановка масок в шаблонах пути
resolve() найти файл, на который указывает ссылка
fnamemodify() преобразовать имя файла
executable() проверка существования исполняемого файла
filereadable() проверка возможности чтения из файла
filewritable() проверка возможности записи в файл
isdirectory() проверка существования каталога
getcwd() получить имя текущего рабочего каталога
getfsize() получить размер файла
getftime() получить время последнего изменения файла
localtime() получить текущее время
strftime() преобразовать время в строку
tempname() получить имя временного файла
delete() удалить файл
rename() переименовать файл
system() получить результат выполнения команды оболочки
hostname() имя системы
Буферы, окна и список файлов-аргументов:
argc() количество аргументов в списке
argidx() текущая позиция в списке аргументов
argv() получить элемент списка аргументов
bufexists() проверка существования буфера
buflisted() проверка существования буфера и его присутствия в списке
bufloaded() проверка существования буфера и состояния его загрузки
bufname() получение имени буфера
bufnr() получение номера буфера
winnr() получение номера текущего окна
bufwinnr() получение номера окна для того или иного буфера
winbufnr() получение номера буфера для определённого окна
getbufvar() получить значение переменной из определённого буфера
setbufvar() установить переменную в определённом буфере
getwinvar() получить значение переменной из определённого окна
setwinvar() установить переменную в определённом окне
Складки:
foldclosed() проверка существования закрытой складки в определённой строке
foldclosedend() то же, что и foldclosed(), но с возвратом последней строки
foldlevel() проверка уровня складки в определённой строке
foldtext() строка, отображаемая для закрытой складки
Подсветка синтаксиса:
hlexists() проверка существования группы подсветки
hlID() получить ID группы подсветки
synID() получить ID синтаксиса в определённой позиции
synIDattr() получить определённый атрибут для ID синтаксиса
synIDtrans() получить преобразованный ID синтаксиса
История:
histadd() добавить элемент в историю
histdel() удалить элемент из истории
histget() получить элемент из истории
histnr() показать максимальный номер в списке истории
Интерактивность:
confirm() предложить пользователю выбор
getchar() получить символ от пользователя
getcharmod() получить модификаторы последнего введённого символа
input() получить строку от пользователя
inputsecret() получить строку от пользователя без отображения на экране
inputdialog() получить строку от пользователя в диалоге
inputresave сохранить и очистить упреждающий буфер ввода
inputrestore() восстановить упреждающий буфер ввода
сервер Vim:
serverlist() возвращает список имён серверов
remote_send() передать команду на сервер Vim
remote_expr() вычислить выражение на сервере Vim
server2client() отправить ответ сервера Vim клиенту
remote_peek() проверить существование ответа сервера Vim
remote_read() прочитать ответ сервера Vim
foreground() переместить окно Vim на передний план
remote_foreground() переместить окно сервера Vim на передний план
Разное:
mode() получить текущий режим редактирования
visualmode() последний использованный Визуальный режим
hasmapto() проверка существования привязки по значению
mapcheck() проверка существования привязки по кнопке
maparg() получение значения привязки
exists() проверка существования переменной, функции  и т.п.
has() проверка поддержки особенности данным Vim
cscope_connection() проверка существования соединения с cscope
did_filetype() проверка использования команды FileType
eventhandler() проверка вызова по событию
getwinposx() Координата X окна Vim в графическом интерфейсе
getwinposy() Координата Y окна Vim в графическом интерфейсе
winheight() получить высоту определённого окна
winwidth() получить ширину определённого окна
libcall() вызов функции из внешней библиотеки
libcallnr() то же самое, с возвратом числа
getreg() получить содержимое регистра
getregtype() получить тип регистра
setreg() назначить тип и содержимое регистра

41.7 Определение функции

Vim также позволяет определять собственные функции. В общем виде объявление функции выглядит так:

:function {имя}({арг1}, {арг2}, ...)
:  {тело_функции}
:endfunction

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

Давайте попробуем определить простую функцию, которая возвращает наименьшее из двух чисел. Определение начинается со строки:

:function Min(num1, num2)

Это указывает Vim, что мы определяем функцию с именем "Min", которая требует два аргумента: "num1" и "num2".

Прежде всего проверим, какое из чисел меньше:

:  if a:num1 < a:num2

Приставка "a:" указывает, что мы используем аргумент функции. Давайте присвоим переменной "smaller" значение наименьшего аргумента:

:  if a:num1 < a:num2
:    let smaller = a:num1
:  else
:    let smaller = a:num2
:  endif

Переменная "smaller" является локальной. Все переменные, которые используются внутри функции, являются локальными, за исключением переменных с приставками, такими как "g:", "a:" или "s:".

Замечание: Для доступа к глобальной переменной из функции вы должны добавить к её имени приставку "g:". Таким образом, "g:count" внутри функции используется для глобальной переменной "count", а "count" это другая переменная, локальная по отношению к данной функции.

Теперь вам следует использовать команду ":return" для возврата значения наименьшего из двух чисел. После этого нужно закончить определение функции:

:  return smaller
:endfunction

Полное определение функции выглядит так:

:function Min(num1, num2)
:  if a:num1 < a:num2
:    let smaller = a:num1
:  else
:    let smaller = a:num2
:  endif
:  return smaller
:endfunction

Вызов функции, определённой пользователем, ничем не отличается от вызова встроенной функции. Различается только имя. Функция Min может быть вызвана так:

:echo Min(5, 8)

Функция будет исполняться и интерпретироваться Vim только в месте её вызова. Если в теле функции допущены ошибки, например используется неопределённая переменная или функция, то вы получите сообщение об ошибке. При определении функции эта ошибка не будет показана.

Когда выполнение функция достигает команды ":endfunction" или при использовании ":return" без аргумента, функция возвращает 0.

Для переопределения уже существующей функции используйте ! в команде ":function":

:function!  Min(num1, num2, num3)
ИСПОЛЬЗОВАНИЕ ДИАПАЗОНА

Команда ":call" может вызываться со строковым диапазоном. Это может означать две вещи. Во-первых, если функция определена со словом "range", то она позаботится о диапазоне сама.

Функции в этом случае будут переданы автоматически переменный "a:firstline" и "a:lastline". Их значениями будут являться номера строк из диапазона, с которым была вызвана функция. Например:

:function Count_words() range
:  let n = a:firstline
:  let count = 0
:  while n <= a:lastline
:    let count = count + Wordcount(getline(n))
:    let n = n + 1
:  endwhile
:  echo "найдено " . count . " слов"
:endfunction

Такую функцию можно вызывать, например, так:

:10,30call Count_words()

Функция будет выполнена один раз и выдаст сообщение о количестве слов.

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

:function  Number()
:  echo "строка " . line(".") . " содержит: " . getline(".")
:endfunction

Если вызвать эту функцию так:

:10,15call Number()

то функция будет вызвана 6 раз.

ПЕРЕМЕННОЕ КОЛИЧЕСТВО АРГУМЕНТОВ

Vim позволяет определять функции с переменным количеством аргументов. Например, следующая команда определяет функцию, которая должна иметь один аргумент (start) и может также иметь до 20 дополнительных аргументов:

:function Show(start, ...)

Переменная "a:1" содержит первый необязательный аргумент, "a:2" второй, и так далее. В переменной "a:0" содержится число дополнительных аргументов.

Например:

:function Show(start, ...)
:  echohl Title
:  echo "Show is " . a:start
:  echohl None
:  let index = 1
:  while index <= a:0
:    echo "  Arg " . index . " is " . a:{index}
:    let index = index + 1
:  endwhile
:  echo ""
:endfunction

В этой функции используется команда ":echohl" для определения подсветки для последующей команды ":echo". ":echohl None" прекращает указанный режим подсветки. Команда ":echon" работает так же как и ":echo", но не переносит строку.

СПИСОК ФУНКЦИЙ

Команда ":function" показывает список имён и аргументов всех определённых пользователем функций:

     :function
<    функция Show(start, ...) ~
     функция GetVimIndent() ~
     функция SetSyn(name) ~

Чтобы посмотреть, что делает функция, используйте её имя в качестве аргумента для команды ":function":

    :function SetSyn
<       функция SetSyn(name) ~
    1       if &syntax == '' ~
    2         let &syntax = a:name ~
    3       endif ~
       endfunction ~
ОТЛАДКА

Номер строки может оказаться полезным при отладке или при получении сообщения об ошибке. См. подробнее об отладочном режиме в |отладка_сценариев|.

Вы также можете установить значение опции 'verbose' в 12 и более, чтобы отслеживать все вызовы функций. Значение 15 и более будет показывать каждую выполняемую строку.

УДАЛЕНИЕ ФУНКЦИИ

Чтобы удалить функцию Show():

:delfunction Show

Если вы пытаетесь удалить несуществующую функцию, то выводится сообщение об ошибке.


41.8 Обработка исключительных ситуаций

Для начала приведём пример:

:try
:   read ~/templates/pascal.tmpl
:catch /E484:/
:   echo "К сожалению, файл-шаблон для Pascal не найден."
:endtry

Команда ":read" не сможет быть выполнена, если файл не существует. Вместо того, чтобы выдавать сообщение об ошибке, приведённый выше фрагмент отлавливает ошибку и выдаёт пользователю симпатичное сообщение.

Все команды между ":try" и ":endtry" вместо ошибок выдают исключительные ситуации. Исключительная ситуация это строка. В случае ошибки такой строкой является сообщение об ошибке. Каждое сообщение об ошибке имеет свой номер. В данном случае, мы ловим ошибку, сообщение о которой содержит "E484:". Этот номер не изменится даже в том случае, если изменится сообщение об ошибке - например, оно может быть переведено на другой язык.

Если выполнение команды ":read" приведёт к какой-либо другой ошибке, то шаблон "E484:" не будет ей соответствовать. В результате, возникшая исключительная ситуация не будет обработана, а будет выдано обычное сообщение об ошибке.

У вас может возникнуть соблазн сделать следующее:

:try
:   read ~/templates/pascal.tmpl
:catch
:   echo "К сожалению, файл-шаблон для Pascal не найден."
:endtry

В этом случае будут отлавливаться все ошибки. Однако, в этом случае вы не сможете видеть полезные сообщение об ошибках, например "E21: Изменения невозможны, так как отключена опция 'modifiable'".

Также может быть полезна и команда ":finally":

:let tmp = tempname()
:try
:   exe ".,$write " . tmp
:   exe "!filter " . tmp
:   .,$delete
:   exe "$read " . tmp
:finally
:   call delete(tmp)
:endtry

Этот фрагмент позволяет пропустить фрагмент буфера от текущей строки до конца файла через некую команду "filter", которая в качестве параметра принимает имя файла. Чтобы ни происходило между командами ":try" и ":finally", пусть даже возникнут исключительные ситуации или если пользователь вдруг решит прервать выполнение команды нажатием CTRL-C, команда "call delete(tmp)" будет выполнена. Это позволяет гарантировать, что временный файл будет удалён.

Более подробно об обработке исключительных ситуаций можно прочитать в справочнике: |исключительные_ситуации-обработка|.


41.9 Ряд замечаний

В этом разделе собрано несколько замечаний относящихся к сценариям Vim. Эта информация также разбросана и по другим разделам, но здесь собрана вместе в виде удобного справочника.

Символ конца строки зависит от операционной системы. На Unix это символ <NL>, на MS-DOS, Windows, OS/2 и т.п. используется пара символов <CR><LF>. Это важно при использовании привязок, которые заканчиваются на <CR>. См. |:source_crnl|.

ПРОБЕЛЫ

Пустые строки в сценариях разрешены и игнорируются.

Пробелы в начале строки (пробелы и табуляция) всегда игнорируются. Пробелы между параметрами (например, между 'set' и 'cpoptions' в примере ниже) сокращаются до одного пробела и выполняют роль разделителя. Пробелы в конце строки могут игнорироваться, а могут и нет, в зависимости от конкретной ситуации; подробнее см. ниже.

Для команды ":set", включающей знак "=" (равно), например:

:set cpoptions    =aABceFst

пробелы перед знаком "=" игнорируются. Однако, после знака "=" пробелов быть не должно!

Для подстановки пробела в значение опции его следует экранировать символом обратной косой черты "\", как показано в следующем примере:

:set tags=мой\ классный\ файл

Тот же пример, записанный в виде

:set tags=мой классный файл

приведёт к возникновению ошибки, поскольку будет интерпретирован как:

:set tags=мой
:set классный
:set файл
КОММЕНТАРИИ

Символ " (двойная кавычка) обозначает начало комментария. Всё, что набрано после этого символа до конца строки, включая сам этот символ, игнорируется, за исключением команд, которые не допускают комментариев, как это показано на примере ниже. Комментарий может начинаться в любой позиции строки.

Некоторые команды не допускают комментариев. Примеры:

:abbrev dev development        " сокращение
:map <F3> o#include         " вставить include
:execute cmd             " выполнить
:!ls *.c             " показать файлы C

Сокращение 'dev' будет преобразовано в 'development     " сокращение'. Привязка для <F3> будет осуществляться для всей строки после 'o# ....', включая '" вставить include'.  Команда "execute" выдаст ошибку.  Команда "!" отправит в оболочку всё, что следует после неё, что приведёт к ошибке оболочки, поскольку " не закрыта.

Комментарии невозможны после команд ":map", ":abbreviate", ":execute" и "!", а также ряда других команд. Для команд ":map", ":abbreviate" и ":execute" можно использовать комментарий так:

:abbrev dev development|" сокращение
:map <F3> o#include|" вставить include
:execute cmd            |" выполнить

При помощи символа '|' одна команда отделяется от другой, которой в этом случае является комментарий.

Обратите внимание, что перед '|' в командах для сокращения и привязки нет пробела. В этих командах используются все символы, которые встречаются до символа '|' или конца строки. Как следствие, вы не всегда видите лишние пробелы в этих командах:

:map <F4> o#include  

Чтобы избежать таких проблем, включайте опцию 'list' при редактировании файлов типа vimrc.

ПОДВОДНЫЕ КАМНИ

В показанном ниже примере возникает ещё более серьёзная проблема:

:map ,ab o#include
:unmap ,ab

Команда unmap в данном случае работать не будет, поскольку она пытается удалить привязку для ",ab ", которой не существует. Такую ошибку очень сложно обнаружить, поскольку пробел на конце ":unmap ,ab " не заметен.

При использовании комментария после команды 'unmap' происходит то же самое:

:unmap ,ab     " комментарий

В данном случае комментарий игнорируется, однако Vim будет пытаться удалить привязку для ',ab     ', которой не существует. Переписать такую команду правильно можно так:

:unmap ,ab|    " комментарий
ВОССТАНОВЛЕНИЕ ВИДА

Иногда вы хотите внести изменение и вернуться к прежнему положению курсора. Было бы неплохо при этом также восстанавливать положение относительно окна, т.е. чтобы при восстановлении положения вверху окна появлялась та же самая строка.

Пример, приведённый ниже, копирует текущую строку, помещает её над первой строкой в файле, и затем восстанавливает вид экрана:

map ,p ma"aYHmbgg"aP`bzt`a

Вот что при этом происходит:

ma"aYHmbgg"aP`bzt`a  
ma установить отметку a в позиции курсора
  "aY скопировать текущую строку в регистр a
     Hmb перейти в верхнюю строку окна и установить отметку b в этой строке
        gg перейти в первую строку файла
          "aP вклеить скопированную строку над первой строкой
             `b вернуться к позиции верхней строки в окне
               zt поместить текст в окне как это было раньше
                 `a переместить курсор в сохранённую раннее позицию
УПАКОВКА ИМЁН

Чтобы имена функций не входили в конфликт с другими функциями, используйте такую схему:

  • Добавьте уникальную строку перед каждым именем функции. Я часто пользуюсь сокращением. Например, "OW_" используется для функций, относящихся к окну опций.
  • Поместите определение функций в одном файле. Установите глобальную переменную, указывающую на то, что функции были загружены. При повторной загрузке файла, прежде всего выгрузите уже загруженные функции.

Пример:

" Пакет XXX

if exists("XXX_loaded")
  delfun XXX_one
  delfun XXX_two
endif

function XXX_one(a)
    ... тело функции ...
endfun

function XXX_two(b)
    ... тело функции ...
endfun

let XXX_loaded = 1

41.10 Написание модулей

Вы можете написать сценарий Vim с тем, чтобы им могли пользоваться многие пользователи. Такой сценарий называется модулем. Пользователи Vim могут поместить ваш модуль в свой каталог plugin и использовать его возможности.  См. |добавление_модуля|.

Вообще, существует два вида модулей:

  • общие модули: Используются для всех типов файлов
  • модули типа файла: Используются только для определённых типов файла

В этом разделе мы рассмотрим модули первого типа. Большинство из рассмотренного материала также применимо и к написанию модулей типов файлов. Особенности модулей типа файла рассматриваются в следующем разделе, см. |написание_модулей_типа_файла|.

ИМЯ

Прежде всего следует выбрать имя для вашего модуля. Имя должно ясно указывать на особенности модуля и, кроме того, должно быть маловероятно, что кто-то напишет модуль с таким-же именем, но выполняющий совершенно иные действия. Чтобы избежать проблем на старых системах Windows, ограничьтесь 8 символами при выборе имени для модуля.

В качестве примера мы будем пользоваться сценарием, который позволяет исправлять опечатки. Мы назовём его "typecorr.vim".

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

ТЕЛО МОДУЛЯ

Начнём с тела модуля, т.е строк, которые выполняют непосредственно работу в нашем сценарии:

14    iabbrev teh the
15    iabbrev otehr other
16    iabbrev wnat want
17    iabbrev synchronisation
18        \ synchronization
19    let s:count = 4

Конечно, список может быть и более длинным.

Номера строк здесь добавлены только для объяснения некоторых особенностей. Не помещайте их в файл сценария!

ЗАГОЛОВОК

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

 1    " Общий модуль Vim для исправления опечаток
 2    " Последнее изменение:    2000 Oct 15
 3    " Автор:        Bram Moolenaar <Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.;

О лицензиях и авторском праве: поскольку модули могут быть крайне полезными, а ограничивать их распространение вряд ли имеет смысл, подумайте о том, чтобы либо сделать их общественным достоянием, либо распространять по лицензии Vim |license|. Короткого замечания в самом начале файла модуля вполне достаточно. Например:

  4    " Лицензия:        Этот файл является общественным достоянием.
ПРОДОЛЖЕНИЕ СТРОКИ, УСТРАНЕНИЕ ПОБОЧНЫХ ЭФФЕКТОВ

В строке 18 используется механизм продолжения строки |продолжение_строки|. Пользователи, работающие в совместимом с Vi режиме (с включённой опцией 'compatible') столкнуться в этом месте с проблемами. Мы не можем отключить опцию 'compatible', поскольку это вызовет целый ряд нежелательных последствий для пользователя. Чтобы их избежать, мы установим опцию 'cpoptions' в её значение по умолчанию и затем восстановим её прежнее значение. Это позволит использовать продолжение строк и модулем смогут пользоваться многие люди. Это делается следующим образом:

11    let s:save_cpo = &cpo
12    set cpo&vim
..
41    let &cpo = s:save_cpo

Прежде всего мы сохраняем старое значение опции 'cpoptions' в переменной s:save_cpo. В конце выполнения модуля это значение будет восстановлено.

Обратите внимание на использование локальной по отношению к сценарию переменной |s:var|. Глобальная переменная может быть уже использована для других целей. Для значений, которые используются только внутри модуля, всегда применяйте локальные переменные.

ОТКАЗ ОТ ЗАГРУЗКИ

Возможно, пользователь не всегда захочет загружать данный модуль. Или системный администратор поместил его в общий для всех пользователей каталог,  а какой-либо пользователь хочет использовать вместо него собственный модуль.  У пользователя должна быть возможность не загружать данный модуль. Это возможно благодаря следующим строкам:

6    if exists("loaded_typecorr")
7      finish
8    endif
9    let loaded_typecorr = 1

Это также позволяет избежать ошибок о переопределении функций и повторном выполнении автокоманд при повторной загрузке модуля.

ПРИВЯЗКИ

Теперь сделаем наш модуль немного интереснее: добавим привязку, выполняющую добавление опечатки к списку в слове, расположенном под курсором. Мы могли бы просто назначить такую привязку для первой понравившейся нам клавиши, но пользователь может уже повесить на эту клавишу свою собственную привязку. Чтобы пользователь мог сам определить клавишу для привязки, воспользуемся элементом <Leader>:

22      map <unique> <Leader>a  <Plug>TypecorrAdd

Элемент "<Plug>TypecorrAdd" будет выполнять требуемую работу, подробнее об этом читайте ниже.

Пользователь может установить значение переменной "mapleader" в последовательность клавиш, которые будут использованы в начале привязки. Например, если пользователь введёт:

let mapleader = "_"

то привязка будет определена как "_a". В противном случае будет использовано значение по умолчанию, обратная косая черта, т.е. будет определена привязка для "\a".

Обратите внимание, что используется <unique>, а значит при попытке переопределения существующей привязки появится сообщение об ошибке. |:map-<unique>|

Но что если пользователь пожелает определить собственную последовательность кнопок? Это можно организовать при помощи такого механизма:

21    if !hasmapto('<Plug>TypecorrAdd')
22      map <unique> <Leader>a  <Plug>TypecorrAdd
23    endif

Иными словами, мы проверяем, существует ли привязка для значения "<Plug>TypecorrAdd" и определяем привязку для "<Leader>a" только в том случае, если она не существует. В этом случае пользователь может поместить в свой файл vimrc:

map ,c  <Plug>TypecorrAdd

чтобы последовательность кнопок для привязки вместо "_a" или "\a" была ",c".

ФРАГМЕНТЫ

Если сценарий становится длинным, то работу над ним можно разбить на отдельные фрагменты. Для этой цели можно использовать функции или привязки. Однако, такие функции и привязки не должны входить в конфликт с другими сценариями. Например, вы можете определить функцию Add(), которая также может быть определена и в другом сценарии. Чтобы этого избежать, определим функцию как локальную по отношению к данному сценарию при помощи приставки "s:".

Итак, определим функцию, которая добавляет новую опечатку:

30    function s:Add(from, correct)
31      let to = input("type the correction for " . a:from . ": ")
32      exe ":iabbrev " . a:from . " " . to
..
36    endfunction

Теперь мы можем вызывать из данного сценария функцию s:Add(). Если другой сценарий также определяет функцию s:Add(), то она будет локальной для того сценария, из которого вызывается. Кроме того, может быть определена глобальная функция Add() (без приставки "s:"), которая также будет отдельной функцией.

В привязках можно использовать <SID>. <SID> это величина, которая уникальна для данного сценария и позволяет его идентифицировать. В нашем случае мы можем использовать <SID> так:

24    noremap <unique> <script> <Plug>TypecorrAdd  <SID>Add
..
28    noremap <SID>Add  :call <SID>Add(expand("<cword>"), 1)<CR>

Теперь, когда пользователь вводит "\a", то выполняются следующие действия:

\a  ->  <Plug>TypecorrAdd  ->  <SID>Add  ->  :call <SID>Add()

В случае, если другой сценарий также использует привязку для <SID>Add, то это будет уже иная привязка, с другим идентификатором сценария.

Обратите внимание, что здесь используется <SID>Add() вместо s:Add(). Это связано с тем, что привязка вводится пользователем, вне файла сценария. <SID> преобразуется в идентификатор сценария, поэтому Vim знает в каком сценарии надо искать функцию Add().

Хотя всё это и выглядит немного сложным, такой механизм требуется, чтобы модуль корректно работал с другими модулями. Основное правило состоит в том, чтобы использовать <SID>Add() в привязках, и s:Add() в остальных местах (в самом сценарии, в автокомандах, командах пользователя).

Мы также можем добавить пункт меню для выполнения тех же функций:

26    noremenu <script> Модули.Добавить\ исправление      <SID>Add

Для добавления относящихся к модулям элементов рекомендуется использовать меню "Модули". В данном случае используется только один элемент. При использовании нескольких элементов рекомендуется создать подменю. Например, "Модули.CVS" можно использовать для модуля, который использует операции CVS "Модули.CVS.поместить", "Модули.CVS.забрать" и т.п.

Обратите внимание, что в строке 28 используется ":noremap" во избежание проблем с другими привязками. Например, кто-то может пожелать переопределить привязку для ":call". В строке 24 мы также используем :noremap", но при этом мы хотим позволить переопределение привязки для "<SID>Add". Вот почему в этом случае мы используем слово "<script>". Это позволяет использовать только привязки, которые определены как локальные для данного сценария. |:map-<script>| Аналогичная операция выполняется и в строке 26 для ":noremenu". |:menu-<script>|

<SID> И <Plug>

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

  • <Plug> видно за пределами сценария. Это слово используется для привязок, к которым пользователь может присвоить собственную последовательность символов. <Plug> образует специальный код, который нельзя воспроизвести нажатием на клавиши.  Чтобы свести возможность ситуации, когда другие модули используют ту же последовательность символов, применяйте такую структуру: <Plug> имя_сценария имя_привязки. В нашем примере имя сценария будет "Typecorr", а имя привязки "Add". В результате получается "<Plug>TypecorrAdd". Чтобы выделить, где начинается имя привязки, мы используем в качестве первого символа прописную букву.
  • <SID> это уникальный идентификатор сценария, его ID. Внутри Vim <SID> преобразуется в нечто похожее на "<SNR>123_", где "123" может быть любым числом. Таким образом, функция "<SID>Add()" может иметь в одном сценарии имя "<SNR>11_Add()" и имя "<SNR>22_Add()" в другом сценарии. Вы можете это заметить, если посмотрите на вывод списка определённых функций по команде ":function". Преобразование <SID> в привязках происходит точно так же, поэтому вы можете вызывать из привязки локальную по отношению к сценарию функцию.
КОМАНДЫ ДЛЯ ПОЛЬЗОВАТЕЛЯ

Теперь добавим команду для добавления исправления:

38    if !exists(":Correct")
39      command -nargs=1  Correct  :call s:Add(<q-args>, 0)
40    endif

Эта команда пользователя будет определена только в том случае, если ещё не существует команды с данным именем. Иначе мы могли бы столкнуться с сообщением об ошибке. Насильное переопределение команды с помощью ":command!" не самая лучшая идея, поскольку пользователь будет недоволен, если его команда работает не так, как ему надо.  |:command|

ПЕРЕМЕННЫЕ В СЦЕНАРИИ

Если переменная начинается с приставки "s:", то это сценарная переменная. Она может быть использована только внутри сценария. Вне сценария эта переменная никак себя не проявляет. Это позволяет избежать проблем с использованием переменных с одинаковым именем в разных сценариях. Переменные сохраняются на протяжении всего сеанса работы Vim. При повторном считывании того же самого сценария будут использованы те же самые переменные.  |s:переменная|

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

19    let s:count = 4
..
30    function s:Add(from, correct)
..
34      let s:count = s:count + 1
35      echo s:count . " исправлений в списке"
36    endfunction

В начале выполнения сценария s:count равняется 4. Затем, при вызове функции s:Add() мы увеличиваем значение s:count на единицу. Не имеет значения, откуда вызвана функция s:Add() -- будут использованы локальные по отношению к сценарию переменные, поскольку сама функция определена внутри сценария.

РЕЗУЛЬТАТ

Вот полный текст нашего модуля:

 1    " Общий модуль Vim для исправления опечаток
2    " Последнее изменение:    2000 Oct 15
3    " Автор:        Bram Moolenaar <Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра.;
4    " Лицензия:     Этот файл является общественным достоянием.
5
6    if exists("loaded_typecorr")
7      finish
8    endif
9    let loaded_typecorr = 1
10
11    let s:save_cpo = &cpo
12    set cpo&vim
13
14    iabbrev teh the
15    iabbrev otehr other
16    iabbrev wnat want
17    iabbrev synchronisation
18        \ synchronization
19    let s:count = 4
20
21    if !hasmapto('<Plug>TypecorrAdd')
22      map <unique> <Leader>a  <Plug>TypecorrAdd
23    endif
24    noremap <unique> <script> <Plug>TypecorrAdd  <SID>Add
25
26    noremenu <script> Модули.Добавить\ исправление      <SID>Add
27
28    noremap <SID>Add  :call <SID>Add(expand("<cword>"), 1)<CR>
29
30    function s:Add(from, correct)
31      let to = input("type the correction for " . a:from . ": ")
32      exe ":iabbrev " . a:from . " " . to
33      if a:correct | exe "normal viws\<C-R>\" \b\e" | endif
34      let s:count = s:count + 1
35      echo s:count . " corrections now"
36    endfunction
37
38    if !exists(":Correct")
39      command -nargs=1  Correct  :call s:Add(<q-args>, 0)
40    endif
41
42    let &cpo = s:save_cpo

Смысл строки 33 еще не объяснялся. Она применяет новое исправление к слову, находящемуся под курсором. Для применения нового сокращения используется команда |:normal|. Обратите внимание, что несмотря на то, что мы вызываем привязку, определённую с помощью ":noremap", в этом случае выполняется подстановка для привязок и сокращений.

Для файлов модулей рекомендуется использовать "unix" в качестве значение опции 'fileformat'. В этом случае сценарий будет работать везде. Сценарии, сохранённые со значением опции 'fileformat' равным "dos", не будут работать на Unix. См. также |:source_crnl|. Чтобы быть уверенным, что всё правильно, перед записью введите команду:

:set fileformat=unix
ДОКУМЕНТАЦИЯ

Для модуля всегда неплохо также написать краткий файл справки, особенно в тех случаях, когда действие модуля может изменяться пользователем. Подробнее о том, как установить файл справки, читайте в |добавление_файла_справки|.

Вот пример справки для нашего модуля, названный "typecorr.txt":

 1    *typecorr.txt*    Модуль для исправления опечаток
 2
 3    Если вы допускаете при наборе текста опечатки, то этот модуль
 4     позволяет исправлять их автоматически.
 5
 6    Модуль распознаёт лишь несколько типичных опечаток. При желании
 7     вы можете добавить свои.
8
9    Привязки:
10    <Leader>a   или   <Plug>TypecorrAdd
11        Добавляет исправление для слова, находящегося под курсором.
12
13    Команды:
14    :Correct {слово}
15        Добавить исправление для {слова}.
16
17                            *typecorr-настройки*
18    Этот модуль не имеет настроек.

Первая строка -- единственная, для которой имеет значение определённый формат. Эта строка будет размещена в разделе "МЕСТНЫЕ ДОПОЛНЕНИЯ:" файла help.txt (см. |local-additions|). Первая "*" должна находиться в первой колонке первой строки. После добавления файла справки выполните команду ":help" и убедитесь, что записи красиво выровнены друг против друга.

Внутри файла справки вы можете добавлять собственные метки внутри **. Однако, будьте внимательны, чтобы не использовать уже определённые раннее метки. Лучше всего использовать внутри метки имя модуля, как в нашем примере: "typecorr-настройки".

Рекомендуется также использовать |вертикальные_линии| для указания ссылок на другие части справочной системы. Это позволяет пользователю легко находить необходимую ему справку.

РАСПОЗНАВАНИЕ ТИПА ФАЙЛА

Если у вас имеется файл, тип которого ещё не распознаётся Vim, то вам следует создать в отдельном файле сценарий определения типа файла. Обычно это специальная автокоманда, которая задаёт тип файла при обнаружении соответствия имени файла шаблону. Например:

au BufNewFile,BufRead *.foo                     set filetype=foofoo

Поместите файл, в котором содержится похожая строчка в первый каталог по пути, заданному в опции 'runtimepath'. В Unix это может быть файл с именем "~/.vim/ftdetect/foofoo.vim".  Соглашение указывает, что в качестве имени сценария следует использовать имя типа файла.

При желании вы можете выполнять и более сложные проверки в сценарии определения типа файла, например проверять содержимое файла для распознавания языка программирования, на котором он написан. См. также |новый_тип_файла|.

РЕЗЮМЕ

Перечислим специальные элементы, которые используются в модулях:

s:name Переменные, локальные по отношению к сценарию
<SID> Идентификатор сценария, используется для привязок и функций, локальных по отношению к сценарию.
hasmapto() Функция, проверяющая наличие привязки для данного значения, которое предлагается сценарием.
<Leader> Значение переменной "mapleader" для определения начала привязки для данного модуля.
:map <unique> Выдаёт предупреждение, если привязка уже существует.
:noremap <script> Использовать только локальные по отношению к данному сценарию привязки, а не глобальные
exists(":Cmd") Проверка существования команды пользователя.

41.11 Написание модуля типа файла

Модуль типа файла похож на модуль общего назначения, с тем исключением, что он устанавливает опции и определяет привязки только для текущего буфера. Подробнее об использовании таких модулей см. в |добавление_модуля_типа_файла|.

Прочитайте внимательно раздел об общих модулях, |41.10|. Всё, что сказано в этом разделе, относится также и к модулям типа файла. Ряд дополнительных особенностей будет изложен в этом разделе. Самое главное, что необходимо усвоить, это то, что модуль типа файла должен работать только в текущем буфере.

ОТКЛЮЧЕНИЕ МОДУЛЯ

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

" Выполнять только в том случае, если еще не сделано для данного
" буфера
if exists("b:did_ftplugin")
  finish
endif
let b:did_ftplugin = 1

Это также позволяет избежать выполнения одного и того же модуля второй раз для данного буфера, что может происходить при использовании команды ":edit" без аргументов.

Теперь пользователи могут отключить загрузку этого модуля, создав свой модуль типа файла с одной-единственной строкой:

let b:did_ftplugin = 1

Это, однако, требует, чтобы каталог с модулями типов файла был указан до каталога $VIMRUNTIME в 'runtimepath'!

Если вы хотите использовать основной модуль для данного типа файлов, но желаете изменить несколько настроек, то вы можете указать свои настройки в файле сценария:

setlocal textwidth=70

Поместите ваш сценарий в каталог "after", чтобы он мог быть прочитан редактором после того, как будет загружен стандартный модуль типа файла "vim.vim" |after-каталог|. На Unix это будет "~/.vim/after/ftplugin/vim.vim". Обратите внимание, что стандартный модуль установит переменную "b:did_ftplugin", так что при повторной загрузке ничего не произойдёт.

ОПЦИИ

Чтобы быть уверенным в том, что модуль типа файла затрагивает только текущий буфер, используйте для установки значения опций команду

:setlocal

и используйте только локальные по отношению к данному буферу опции (не ленитесь заглядывать в справочник). При использовании |:setlocal| для глобальных опций или опций, которые являются локальными по отношению к окну, значение будет изменяться для других буферов, а модуль типа файла не должен этого делать.

При использовании опций, значение которых представлено списком нескольких элементов или флагов, подумайте о возможности использования "+=" и "-=" для сохранения других существующих значений. Имейте в виду, что пользователь может уже изменить значение той или иной опции. Часто может потребоваться сброс значения опции к значению, используемому по умолчанию, и затем установке дополнительных значений опции. Пример: >

:setlocal formatoptions& formatoptions+=ro
ПРИВЯЗКИ

Чтобы быть уверенным в том, что привязки будут работать только в текущем буфере, используйте команду

:map <buffer>

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

if !hasmapto('<Plug>JavaImport')
  map <buffer> <unique> <LocalLeader>i <Plug>JavaImport
endif
noremap <buffer> <unique> <Plug>JavaImport oimport ""<Left><Esc>

|hasmapto()| используется для проверки существования привязки для значения <Plug>JavaImport. Если привязки не существует, то используется привязка по умолчанию. Эта привязка начинается со слова |<LocalLeader>|, которое позволяет пользователю выбрать клавиши, с которых должны начинаться привязки для модулей данного типа файла. Значением по умолчанию является символ обратной косой черты.

Слово <unique> используется для вывода сообщения об ошибке в том случае, если привязка уже существует, или конфликтует с другой существующей привязкой. |:noremap| используется во избежание конфликта с другими привязками, определёнными пользователем. Вы можете использовать ":noremap <script>" для того, чтобы позволить переопределение привязок, начинающихся с <SID> в данном сценарии.

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

" Добавить привязки, если пользователь не возражает.
if !exists("no_plugin_maps") && !exists("no_mail_maps")
  " Цитировать текст при помощи "> "
  if !hasmapto('<Plug>MailQuote')
    vmap <buffer> <LocalLeader>q <Plug>MailQuote
    nmap <buffer> <LocalLeader>q <Plug>MailQuote
  endif
  vnoremap <buffer> <Plug>MailQuote :s/^/> /<CR>
  nnoremap <buffer> <Plug>MailQuote :.,$s/^/> /<CR>
endif

Используется две глобальные переменные:

no_plugin_maps отключает привязки для всех модулей типа файла
no_mail_maps отключает привязки для определённого модуля типа файла
КОМАНДЫ ПОЛЬЗОВАТЕЛЯ

Для добавления команды пользователя для определённого типа файла, чтобы она могла быть использована только в данном буфере, используйте аргумент "-buffer" для команды |:command|. Пример:

:command -buffer  Make  make %:r.s
ПЕРЕМЕННЫЕ

Модуль типа файла считывается для каждого буфера с данным типом файла. Локальные переменные |s:переменная| используются всеми модулями данного типа совместно. Если вы хотите использовать какую-либо переменную строго в пределах данного буфера, то используйте буферные переменные |b:переменная|.

ФУНКЦИИ

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

:if !exists("*s:Func")
:  function s:Func(arg)
:    ...
:  endfunction
:endif
ОТМЕНА

Если пользователь вводит команду вроде ":setfiletype xyz", то эффект предыдущего определения типа должен быть отменён. Для этого можно установить значение переменной b:undo_ftplugin равным списку команд, которые должны быть выполнены, чтобы отменить действие модуля типа файла. Например:

let b:undo_ftplugin = "setlocal fo< com< tw< commentstring<"
    \ . "| unlet b:match_ignorecase b:match_words b:match_skip"

Использование команды ":setlocal" с символом "<" после имени опции позволяет вернуть опции её глобальное значение. Это как правило самый лучший способ восстановить значение опции.

Этот пример требует, чтобы в значении 'cpoptions' не было флага "C", чтобы разрешить продолжение строки, как об этом говорилось выше |use-cpo-save|.

ИМЯ ФАЙЛА

Тип файла должен быть отображён в имени файла модуля,  см. |модуль_типа_файла-имя|. Вы можете использовать одну из трёх форм:

.../ftplugin/stuff.vim
.../ftplugin/stuff_foo.vim
.../ftplugin/stuff/bar.vim

здесь "stuff" указывает на тип файла, а "foo" и "bar" это произвольные имена.

РЕЗЮМЕ

Перечислим специальные элементы, которые используются в модулях типа файла:

<LocalLeader> Значение переменной "maplocalleader", с помощью которой пользователь определяет клавиши, с которых начинаются привязки для модулей типа файла.
:map <buffer> Определить локальную для данного буфера привязку.
:noremap <script> Допускать только переопределение привязок, которые в модуле начинаются с <SID>.
:setlocal Установить опции только для текущего буфера.
:command -buffer Определить команду пользователя локально для данного буфера.
exists("*s:Func") Проверяет существование данной функции.

См. также |модули-специальные_элементы| для получения информации об элементах, которые применяются во всех модулях.


41.12 Написание модуля для компилятора

Модуль для компилятора настраивает опции для использования с определённым компилятором. Пользователь загружает такой модуль при помощи команды |:compiler|. Его основное предназначение состоит в настройке значения опций 'errorformat' и 'makeprg'.

Проще всего показать это на примере. Вот команда, которая позволяет редактировать все стандартные модули для компиляторов:

:next $VIMRUNTIME/compiler/*.vim

Для перехода к следующему модулю используйте команду |:next|.

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

:if exists("current_compiler")
:  finish
:endif
:let current_compiler = "mine"

Если вы напишите свой сценарий для компилятора и поместите его в личный каталог для этих модулей (на Unix это, например, ~/.vim/compiler), то в этом файле вам нужно будет установить переменную "current_compiler", чтобы не загружать сценарий, используемый для данного компилятора по умолчанию.

Второй механизм заключается в применении ":set" при выполнении команды ":compiler!" и ":setlocal" при выполнении команды ":compiler". Vim определяет для этого пользовательскую команду ":CompilerSet". Однако, старые версии Vim этого не делают, и в этом случае вам придётся определить эту команду самостоятельно. Приведём пример:

if exists(":CompilerSet") != 2
    command -nargs=* CompilerSet setlocal <args>
endif
CompilerSet errorformat&            " использовать значение опции
                                    " 'errorformat' по умолчанию
CompilerSet makeprg=nmake

Используйте описанный выше механизм для написании модуля компилятора, который используется по умолчанию в системе или распространяется в комплекте поставки Vim. Если пользователь определит переменную "current_compiler" в собственном модуле, то системный модуль считываться не будет.

Если ваш модуль для компилятора только изменяет некоторые настройки модуля, который используется по умолчанию, то вам не следует проверять в нём значение  переменной "current_compiler". Такой модуль будет загружаться в последнюю очередь, поэтому он должен находиться в каталоге, указанном в 'runtimepath' в самом конце. В Unix это может быть ~/.vim/after/compiler.


Следующая глава: Добавление новых меню
Авторские права: см. Авторские права