Создание сценариев для редактора Vim: Часть 1. Переменные, значения и выражения

Знакомство с базовыми элементами Vimscript

Vimscript – это механизм модификации и расширения редактора Vim посредством написания сценариев. С помощью сценариев можно создавать новые инструменты, упрощать выполнение типовых задач и даже изменять функциональность самого редактора. Эта статья (первая из серии) познакомит вас с базовыми компонентами языка программирования Vimscript, такими как переменные, значения, выражения, функции и команды. Эти возможности последовательно демонстрируются и объясняются на простых примерах.

Дэмиан Конвей, руководитель и ведущий преподаватель, Thoughtstream

author photo - damian conwayДэмиан Конвей является адъюнкт-профессором компьютерных наук в университете Монаш, Австралия, а также главой международной компании Thoughtstream, занимающейся обучением в сфере ИТ. Он ежедневно пользуется редактором vi на протяжении четверти века и теперь уже почти не надеется, что сможет побороть эту пагубную привычку.



25.08.2009

О Vimscript и этой серии статей

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

Отличный текстовый редактор

Есть старая шутка о том, что Emacs был бы отличной операционной системой, если бы в нем был приличный текстовый редактор, а Vi был бы отличным текстовым редактором, если бы в нем была достойная операционная система. В этой остроте отражено главное стратегическое преимущество Emacs над vi: встроенный язык программирования расширений. Действительно, тот факт, что пользователи Emacs с радостью осваивают умопомрачительные «аккорды» для управления редактором и пишут для него расширения на Lisp, показывает, насколько ценным преимуществом является встроенный язык создания расширений.

Но любителям vi больше нет нужды бросать завистливые взгляды на неуклюжий язык сценариев Emacs. Для нашего любимого редактора тоже можно создавать сценарии, причем гораздо более удобным способом, чем в Emacs.

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

Далее я буду предполагать, что у вас есть доступ к Vim и что вы знакомы с его интерактивными возможностями. Если это не так, то начать знакомиться с ним можно, используя Web-сайт Vim, различные онлайн-ресурсы и печатные книги (см. ссылки в разделе Resources), или же или же просто набрав :help внутри самого Vim.

Во всех примерах, приведенных в этой серии статей, предполагается использование Vim версии 7.2 или выше, если не указано обратное. Узнать используемую версию Vim можно командой:

vim --version

в консоли Linux, или набрав :version внутри самого Vim. Если вы используете более старую версию Vim, настоятельно рекомендуется обновить его до последнего выпуска, так как в старых версиях не поддерживаются многие из рассматриваемых здесь возможностей Vimscript. Скачать или обновить Vim можно по ссылке из раздела Ресурсы.


Vimscript

Язык создания сценариев для редактора Vim, известный как Vimscript, - это типичный динамический командный (императивный) язык, предоставляющий большую часть функциональности обычного языка: переменные, выражения, управляющие структуры, встроенные функции, пользовательские функции, полнофункциональные строки, высокоуровневые структуры данных (списки и словари), файловый и консольный ввод/вывод, работу с регулярными выражениями, исключения и встроенный отладчик.

Вы можете почитать имеющуюся в Vim документацию по Vimscript, набрав в редакторе команду:

:help vim-script-intro

Или же просто читайте дальше.

Запуск сценариев для Vim

Есть несколько способов выполнения команд сценариев Vim. Самый простой способ – поместить их в файл (как правило, с расширением .vim) а затем выполнить его из сеанса редактора с помощью команды :source. Например:

:source /full/path/to/the/scriptfile.vim

Другой вариант – набирать команды непосредственно в командной строке Vim после двоеточия, как показано ниже:

:call MyBackupFunc(expand('%'), { 'all':1, 'save':'recent'})

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

:nmap ;s :source /full/path/to/the/scriptfile.vim<CR>

:nmap \b :call MyBackupFunc(expand('%'), { 'all': 1 })<CR>

Подобные команды обычно помещаются в инициализационный файл .vimrc в домашней директории. Теперь при нахождении в нормальном режиме (т.е. не в режиме ввода текста) при нажатии последовательности ;s будет выполняться указанный файл сценария, а при нажатии \b будет вызываться функция MyBackupFunc() (предположительно также определенная где-то в файле .vimrc).

Для вызова всех сценариев из приведенных в этой статье примеров используются различные типы сочетаний клавиш. В следующих статьях мы рассмотрим также два других распространенных метода – запуск сценариев через двоеточие из командной строки Vim и использование событий редактора для автоматического запуска сценариев.


Пример работы с синтаксисом

Vim имеет очень богатые и сложные возможности по подсветке синтаксиса, которые можно включать с помощью команды :syntax enable и выключать с помощью команды :syntax off.

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

Листинг 1. Переключение подсветки синтаксиса
function! ToggleSyntax()
   if exists("g:syntax_on")
      syntax off
   else
      syntax enable
   endif
endfunction

nmap <silent>  ;s  :call ToggleSyntax()<CR>

Теперь можно включать и выключать подсветку из нормального режима сочетанием клавиш ;s. Давайте рассмотрим каждый компонент этого сценария.

Очевидно, что в первом блоке объявляется пользовательская функция ToggleSyntax(), не принимающая никаких аргументов. В этой функции сначала вызывается встроенная функция exists(), которой передается строка. Функция exists() определяет, задана ли переменная с определенным именем (в данном случае глобальная переменная g:syntax_on).

Если переменная задана, то в инструкции if выполняется команда syntax off, иначе выполняется команда syntax enable. Так как syntax enable задает переменную g:syntax_on, а syntax off ее удаляет, то, вызывая функцию ToggleSyntax(), можно включать и выключать подсветку синтаксиса.

После этого остается только назначить сочетание клавиш (в данном случае ;s ) для вызова функции ToggleSyntax():

nmap <silent> ;s :call ToggleSyntax()<CR>

nmap означает "normal-mode key mapping". Параметр <silent>, следующий за nmap, говорит о том, что при выполнении команды не нужно выводить информацию в консоль, - это гарантирует, что сочетание клавиш ;s будет выполнять свою работу ненавязчиво. Эта работа заключается в выполнении следующей команды:

:call ToggleSyntax()<CR>

Так в Vimscript следует вызывать функцию, если вы хотите игнорировать возвращаемое значение.

Заметьте, что <CR> в конце команды – это действительно последовательность символов <,C,R,>. Vimscript считает ее эквивалентом символа возврата каретки. Кроме этого, Vimscript понимает также множество других подобных представлений непечатаемых символов. Например, вот так можно сделать, чтобы в редакторе пробел действовал как клавиша page-down (как это работает в большинстве Web-браузеров):

:nmap <Space> <PageDown>

Вы можете просмотреть полный список этих специальных символов, набрав в Vim :help keycodes

Также заметьте, что ToggleSyntax() может вызывать встроенную команду syntax напрямую. Это возможно потому, что каждая вызываемая через двоеточие встроенная команда также автоматически является инструкцией в Vimscript. Например, чтобы облегчить создание в Vim выровненных по центру заголовков документов, можно создать функцию, которая переводит каждое слово на текущей строке в верхний регистр, выставляет выравнивание по центру, после чего переходит на следующую строку:

Листинг 2. Создание выровненных по центру заголовков
function! CapitalizeCenterAndMoveDown()
   s/\<./\u&/g   "Встроенный механизм подстановки переводит каждое слово в верхний регистр
   center        "Встроенная команда center задает выравнивание строки по центру
   +1            "Встроенная команда относительного перемещения (на +1 строку вниз)
endfunction

nmap <silent>  \C  :call CapitalizeCenterAndMoveDown()<CR>

Инструкции Vimscript

Как видно из предыдущих примеров, в Vimscript все инструкции завершаются символом новой строки (как в shell-скриптах или в Python). Если вам нужно записать инструкцию на нескольких строках, используйте для продолжения строки одиночный обратный слеш. Необычным по сравнению с другими языками здесь является то, что обратный слеш должен находиться не в конце продолжаемой строки, а в начале новой строки:

Листинг 3. Продолжение строк с помощью обратного слеша
call SetName(
\             first_name,
\             middle_initial,
\             family_name
\           )

Также можно поместить несколько инструкций на одной строке, разделив их вертикальной чертой:

echo "Starting..." | call Phase(1) | call Phase(2) | echo "Done"

Таким образом, в Vimscript вертикальная черта эквивалентна точке с запятой в большинстве других языков программирования. К сожалению, в Vim нельзя для этих целей использовать точку с запятой, потому что этот символ имеет в редакторе специальное значение (а именно, при задании интервала выполнения команды он означает "с текущей строки до...").

Комментарии

Одним из важных вариантов использования вертикальной черты как разделителя инструкций является комментирование. Комментарии в Vimscript начинаются с двойной кавычки и продолжаются до конца строки. Например:

Листинг 4. Комментарии в Vimscript
if exists("g:syntax_on")
   syntax off      "а не команда 'syntax clear' (которая выполняет нечто другое)
else
   syntax enable   "а не команда 'syntax on' (которая переопределяет цветовую схему)
endif

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

echo "> " "Print generic prompt

Команда echo ожидает появления одной или нескольких строк, поэтому этот код генерирует ошибку, сообщающую, что во второй строке (по мнению Vim) отсутствует закрывающая кавычка.

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

echo "> " |"Print generic prompt

Значения и переменные

В Vimscript присваивание переменным значений осуществляется с помощью специального ключевого слова let:

Листинг 5. Использование ключевого слова let
let name = "Damian"

let height = 165

let interests = [ 'Cinema', 'Literature', 'World Domination', 101 ]

let phone     = { 'cell':5551017346, 'home':5558038728, 'work':'?' }

Заметьте, что для строк в качестве разделителей можно использовать как одиночные, так и двойные кавычки. В строках с двойными кавычками можно использовать специальные escape-последовательности, такие как "\n" (для символа новой строки), "\t" (для табуляции), "\u263A" (для смайлика в Unicode), или "\<ESC>" (для символа escape). В строках с одиночными кавычками всё находящееся между разделителями воспринимается буквально, за исключением двух последовательных одиночных кавычек, которые воспринимаются как символ одиночной кавычки.

Значения в Vimscript обычно бывают одного из следующих трех типов:

  • скаляр: одиночное значение, такое как строка или число. Например: "Damian" или 165
  • список: упорядоченная последовательность значений, указываемая в квадратных скобках, например: ['Cinema', 'Literature', 'World Domination', 101]. Элементы списка имеют целочисленные индексы, начинающиеся с нуля.
  • словарь: неупорядоченный набор пар строковой ключ - значение, указываемый в фигурных скобках. Например: {'cell':5551017346, 'home':5558038728, 'work':'?'}

Заметьте, что значения в списке или словаре не обязательно должны быть одного типа, можно смешивать в них строки, числа и даже при желании вложенные списки и словари.

В отличие от значений, переменные изначально не имеют типа. Они принимают тип первого назначаемого им значения. Так, в предыдущем примере переменные name и height становятся скалярами (т.е. впоследствии могут принимать только строковые или числовые значения), interests принимает тип списка (т.е. теперь она может хранить только списки), а phone принимает тип словаря (и может хранить только словари). Тип переменной жестко задается на все время выполнения, и последующие попытки его поменять будут приводить к ошибке:

let interests = 'unknown' " Error: variable type mismatch

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

Таблица 1. Области видимости переменных в Vimscript
ПрефиксЗначение
g:имя_переменной Переменная является глобальной
s:имя_переменной Переменная является глобальной для текущего файла сценария
w:имя_переменной Переменная является локальной для текущего окна редактора
t:имя_переменной Переменная является локальной для текущей вкладки редактора
b:имя_переменной Переменная является локальной для текущего буфера редактора
l:имя_переменной Переменная является локальной для текущей функции
a:имя_переменной Переменная является параметром текущей функции
v:имя_переменной Переменная является преодпределенной переменной Vim

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

Таблица 2. Псевдопеременные Vimscript
ПрефиксЗначение
&имя_переменной параметр Vim (локальный, если указана соответствующая опция, иначе глобальный)
&l:имя_переменной локальный параметр Vim
&g:имя_переменной глобальный параметр Vim
@имя_переменной реестр Vim
$имя_переменной Переменная окружения

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

nmap <silent> ]] :let &tabstop += 1<CR>

nmap <silent> [[ :let &tabstop -= &tabstop > 1 ? 1 : 0<CR>

Выражения

Заметьте, что при задании действия для сочетания клавиш [[ в предыдущем примере использовано C-подобное «тернарное выражение»:

&tabstop > 1 ? 1 : 0

Здесь оно предотвращает уменьшение количества пробелов в символе табуляции меньше разумной нижней границы 1. Как можно видеть из примеров, выражения в Vimscript составляются с помощью тех же самых основных операторов, которые используются в большинстве других современных скриптовых языков, и примерно с тем же самым синтаксисом. Имеющиеся операторы (сгруппированные по возрастанию приоритета) показаны в таблице 3.

Таблица 3. Приоритет операторов в Vimscript
ОперацияСинтаксис оператора
Присваивание
Числовое сложение и присваивание
Числовое вычитание и присваивание
Конкатенация строк и присваивание
let var=expr
let var+=expr
let var-=expr
let var.=expr
Тернарный (условный) оператор bool?expr-if-true:expr-if-false
Логическое ИЛИ bool||bool
Логическое И bool&&bool
Проверка строк или чисел на равенство
Проверка строк или чисел на неравенство
Проверка строк или чисел «больше»
Проверка строк или чисел «больше или равно»
Проверка строк или чисел «меньше»
Проверка строк или чисел «меньше или равно»
expr==expr
expr!=expr
expr>expr
expr>=expr
expr<expr
expr<=expr
Числовое сложение
Числовое вычитание
Конкатенация строк
num+num
num-num
str.str
Числовое умножение
Числовое деление
Деление чисел по модулю
num*num
num/num
num%num
Преобразование в число
Получение противоположного числа
Логическое НЕ
+num
-num
!bool
Скобки (для изменения приоритета операторов) (expr)

Особенности работы логических операторов

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

При использовании строки как булева значения она сначала конвертируется в число, которое переводится в логическую истину (не ноль) или ложь (ноль). Это приводит к тому, что подавляющее большинство строк – в том числе большинство непустых строк, в булевом контексте являются ложью. Довольно типична следующая ошибка в проверке строки на пустоту:

Листинг 6. Ошибочная проверка пустоты строки
let result_string = GetResult();

if !result_string
   echo "No result"
endif

Это работает правильно, когда переменной result_string присваивается пустая строка, но проблема заключается в том, что также "No result" выдается и для таких строк как "I am NOT an empty string". Это происходит потому, что строка сначала конвертируется в число (ноль), а уже из него в булево значение (ложь).

Правильным решением является явная проверка строк на пустоту с помощью соответствующей встроенной функции:

Листинг 7. Правильная проверка пустоты строки
if empty(result_string)
   echo "No result"
endif

Особенности работы операторов сравнения

В Vimscript операторы сравнения всегда производят числовое сравнение, за исключением случая, когда оба операнда являются строками. В частности, если одним из операндов является строка, а другим – число, то строка будет приведена к числу, после чего будет выполнено числовое сравнение этих двух операндов. Это может приводить к неочевидным ошибкам вроде:

let ident = 'Vim'

if ident == 0 "Всегда истина (так как строка 'Vim' конвертируется в число 0)

Более надежное решение для подобных случаев выглядит так:

if ident == '0'   "если ident - строка, то используется сравнение строк,"а иначе числовое сравнение

Обычное сравнение строк в Vim производится с учетом локального параметра ignorecase. Однако в любом случае можно явно задать, следует ли учитывать регистр символов при сравнении, добавив # (учитывать) или ? (не учитывать).

Листинг 8. Работа с регистром символов при сравнении строк
if name ==? 'Batman'         |"Проверка на равенство без учета регистра
   echo "I'm Batman"
elseif name <# 'ee cummings' |"Проверка "меньше, либо равно" с учетом регистра
   echo "the sky was can dy lu minous"
endif

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

Особенности работы арифметических операторов

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

Листинг 9. Проблема с целочисленной арифметикой
"Проходим по файлам...
for filenum in range(filecount)
   " Выводим информацию о прогрессе...
   echo (filenum / filecount * 100) . '% done'" Обрабатываем файл...
   call process_file(filenum)
endfor

Так как filenum всегда будет меньше, чем filecount, то целочисленное деление filenum/filecount всегда будет давать ноль, поэтому в каждой итерации цикла будет выводиться строка:

Now 0% done

Даже в версии 7.2 Vim осуществляет вычисления с плавающей точкой, только если явно указано, что один из операндов является значением с плавающей точкой:

let filecount = 234

echo filecount/100   |" будет выведено 2
echo filecount/100.0 |" будет выведено 2.34

Другой пример переключения

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

Например, можно назначить сочетание клавиш (скажем ;p), которое бы представляло текст из предыдущего абзаца следующим образом:

It's easy to adapt the syntax-toggling script shown earlier to create other useful tools. For example, if there is a set of words that you frequently misspell or misapply, you could add a script toyour .vimrc to activate Vim's match mechanism and highlight problematic words when you're proofreading text.

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

Листинг 10. Подсветка слов, часто используемых неправильно
"Задаем стиль подсветки текста...
highlight STANDOUT term=bold cterm=bold gui=bold

"Список проблемных слов...
let s:words = [
             \ "it's",  "its",
             \ "your",  "you're",
             \ "were",  "we're",   "where",
             \ "their", "they're", "there",
             \ "to",    "too",     "two"
             \ ]

"Создаем команду Vim для нахождения проблемных слов...
let s:words_matcher
\ = 'match STANDOUT /\c\<\(' . join(s:words, '\|') . '\)\>/'

"Создаем функцию для включения и выключения режима проверки...
function! WordCheck ()
   "Переключаем состояние флага (или задаем его, если он еще не существует)...
   let w:check_words = exists('w:check_words') ? !w:check_words : 1

   "В соответствии с новым состоянием флага включаем или выключаем проверку слов...
   if w:check_words
      exec s:words_matcher
   else
      match none
   endif
endfunction

"Задаем сочетание клавиш ;p для переключения проверки...
nmap <silent> ;p :call WordCheck()<CR>

Переменная w:check_words используется как булев флаг для включения или выключения проверки слов. Код в первой строке функции WordCheck() проверяет, существует ли флаг, и если он существует, меняет его значение на противоположное:

let w:check_words = exists('w:check_words') ? !w:check_words : 1

Если же w:check_words еще не существует, он создается с начальным значением 1.

let w:check_words = exists('w:check_words') ? !w:check_words : 1

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

Проверка слов в Vim включается с помощью команды match. Для match следует указать спецификацию подсветки цвета (в этом примере STANDOUT), и регулярное выражение, описывающее текст, который следует подсвечивать. В нашем случае в этом регулярном выражении перечисляются через логическое ИЛИ все слова, указанные в списке s:words (вот так: join(s:words, '\|')). Затем это выражение помещается в скобки (\c\<\(...\)\>), чтобы по нему находились только целые слова из указанного набора, причем без учета регистра символов.

Затем функция WordCheck() конвертирует получившуюся строку в команду Vim и выполняет ее (exec s:words_matcher) чтобы включить подсветку. А если флаг w:check_words выключен, функция выполняет команду match none, отключающую поиск слов.


Создание сценариев для работы в режиме ввода текста

С помощью Vimscript можно создавать сценарии не только для нормального режима. Также можно с помощью команд imap или iabbrev назначать сочетания клавиш или аббревиатуры, которые можно использовать при вводе текста. Например:

imap <silent> <C-D><C-D> <C-R>=strftime("%e %b %Y")<CR>

imap <silent> <C-T><C-T> <C-R>=strftime("%l:%M %p")<CR>

После добавления этих строк в файл .vimrc, двойное нажатие клавиш CTRL-D в режиме ввода текста будет вызывать встроенную функцию strftime() и вставлять в текст текущую дату, а двойное нажатие CTRL-T аналогичным образом будет вставлять текущее время.

С помощью такого же общего шаблона можно организовать выполнение любого сценария при вводе назначенного сочетания клавиш или аббревиатуры. Просто поместите соответствующее выражение Vimscript или вызов функции между открывающей последовательностью <C-R>= (которая командует Vim вставить результат последующего вычисления) и завершающей последовательностью <CR> (которая командует Vim вычислить результат предыдущего выражения). Обратите внимание, что в Vim <C-R> (обозначение сочетания клавиш CTRL-R) - это не то же самое, что <CR> (обозначение символа возврата каретки).

Например, используя встроенную функцию Vim getcwd(), можно следующим образом создать аббревиатуру для текущей рабочей директории:

iabbrev <silent> CWD <C-R>=getcwd()<CR>

Или же можно встроить простой калькулятор, вызываемый при нажатии CTRL-C в режиме ввода текста:

imap <silent> <C-C> <C-R>=string(eval(input("Calculate: ")))<CR>

Здесь в выражении:

string( eval( input("Calculate: ") ) )

сначала вызывается встроенная функция input(), предлагающая пользователю ввести то, что надо посчитать, и возвращающая полученную от пользователя строку. Затем эта строка передается во встроенную функцию eval(), которая вычисляет значение переданного ей выражения Vimscript и возвращает результат. Затем встроенная функция string() конвертирует числовой результат обратно в строку, которую вставляет в текст управляющая последовательность <C-R>=.

Более сложные сценарии для режима ввода текста

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

Например, можно поменять поведение сочетания клавиш CTRL-Y в режиме ввода текста. Обычно CTRL-Y в режиме ввода выполняет "вертикальное копирование", т.е. копирует и вставляет символ, находящийся на той же позиции в строке, расположенной на одну выше курсора. Например, в следующей ситуации CTRL-Y вставит на позицию курсора символ "m":

Glib jocks quiz nymph to vex dwarf
Glib jocks quiz ny_

Однако, возможно, вы предпочтете игнорировать при вертикальном копировании все промежуточные пустые строки и копировать символ с той же вертикальной позиции из первой непустой строки, находящейся выше места вставки - т.е. чтобы в следующей ситуации CTRL-Y также вставил "m", хотя находящаяся непосредственно выше строка является пустой:

Glib jocks quiz nymph to vex dwarf

Glib jocks quiz ny_

Чтобы добиться подобного поведения, поместите следующий код в ваш .vimrc файл:

Листинг 11. Добавление игнорирования пустых строк при вертикальном копировании
"Находим и возвращаем символ, находящийся "над" текущей позицией курсора...
function! LookUpwards()
   "Определяем текущий столбец и целевую строку для копирования...
   let column_num      = virtcol('.')
   let target_pattern  = '\%' . column_num . 'v.'
   let target_line_num = search(target_pattern . '*\S', 'bnW')

   "Если целевая строка найдена, возвращаем вертикально скопированный символ...
if !target_line_num return "" else return matchstr(getline(target_line_num), target_pattern) endif endfunction "Переопределяем действие CTRL-Y в режиме ввода текста...
imap <silent> <C-Y> <C-R><C-R>=LookUpwards()<CR>

Функция LookUpwards() сначала определяет с помощью встроенной функции virtcol(), вертикальную позицию (также называемую "виртуальным столбцом") места, куда будет осуществляться вставка. Аргумент '.' указывает, что мы хотим получить номер столбца для текущего положения курсора:

let column_num = virtcol('.')

Затем LookUpwards() использует встроенную функцию search() для поиска по файлу в обратном направлении, начиная с текущей позиции курсора:

let target_pattern = '\%' . column_num . 'v.'
let target_line_num = search(target_pattern . '*\S', 'bnW')

Для поиска ближайшей сверху строки используется специальный шаблон \%column_numv.*\S. Этот шаблон задает поиск в строке непробельного символа (\S), находящегося на одной вертикальной позиции с курсором (\%column_numv) или после него (.*). Второй аргумент функции search(), - это конфигурационная строка bnW, которая предписывает функции искать в обратном направлении (backwards), но не перемещать (not to move) курсор и по достижении начала файла не продолжать поиск с конца файла (wrap). В случае результативного поиска search() возвращает номер найденной строки, а в противном случае - ноль.

Далее в инструкции if определяется символ (если такой есть), который следует скопировать на текущую позицию. Если подходящей строки не было найдено, то переменная target_line_num равна нулю, в результате будет выполнена первая инструкция return, возвращающая пустую строку (которая значит, что ничего не будет вставлено).

Если же подходящая строка была найдена, выполняется вторая инструкция return. В ней сначала из буфера редактора извлекается копия найденной строки:

return matchstr(getline(target_line_num), target_pattern)

Затем из этой строки извлекается по шаблону и возвращается нужная подстрока из одного символа:

return matchstr(getline(target_line_num), target_pattern)

Реализовав новое поведение вертикального копирования внутри функции LookUpwards(), теперь только остается с помощью imap переопределить стандартную команду CTRL-Y в режиме ввода:

imap <silent> <C-Y> <C-R><C-R>=LookUpwards()<CR>

Обратите внимание, что ранее в примерах imap для указания вызова функции Vimscript использовалась строка <C-R>=, а в этом примере вместо нее используется <C-R><C-R>=. В случае формы с одиночным CTRL-R результат последующего выражения вставляется так, как будто бы он был набран непосредственно с клавиатуры. Значит, все содержащиеся в результате специальные символы сохраняют значение и поведение. В случае же формы с двойным CTRL-R текст результата вставляется с точностью до символа, без какой-либо последующей обработки.

В этом примере нам больше подходит второй вариант, так как нашей целью является точное копирование текста, находящегося выше курсора. Если бы мы использовали <C-R>=, то копирование с предыдущей строки символа escape было бы эквивалентно его вводу с клавиатуры, т.е. привело бы к выходу из меню ввода.

Изучение встроенных функций Vim

Как видно из всех предыдущих примеров, значительная часть возможностей Vimscript заключается во встроенных функциях (которых насчитывается более 200). Начать их изучение можно, набрав в редакторе:

:help functions

или (что более полезно) просмотрев сгруппированный по категориям список функций:

:help function-list


Заглядывая вперед

Vimscript – это механизм преобразования и расширения редактора Vim. Создание сценариев дает возможность создавать новые инструменты (такие как подсветка проблемных слов), упрощать выполнение типовых задач (таких как изменение количества пробелов в символе табуляции, вставка в текст времени и даты или переключение подсветки синтаксиса) и даже изменять существующие возможности редактора (например, расширяя функциональность копирования с предыдущей строки с помощью CTRL-Y).

Многим людям легче всего изучать новый язык на примерах. Для целей обучения можно использовать огромное количество примеров сценариев, находящихся найти на вики-страничке Vim Tips. Большинство из них сами по себе уже являются полезными инструментами. Более крупные примеры сценариев можно найти в архиве скриптов Vim, содержащем более 2000 проектов. Соответствующие ссылки можно найти ниже в разделе Ресурсы.

Если вы уже знакомы с Perl, Python, Ruby, PHP, Lua, Awk, Tcl или любым языком оболочки, то Vimscript будет вам казаться до боли знакомым (в общем подходе и концепциях) и в то же время удручающе отличающимся (в некоторых особенностях синтаксиса). Чтобы преодолеть эту двойственность ощущений и овладеть языком Vimcsript, вам придется потратить некоторое время, экспериментируя, изучая и играя с ним. Почему бы для этих целей вам не попытаться самостоятельно разработать сценарий, чтобы улучшить какое-нибудь поведение Vim, которое больше всего вас не устраивает?

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

Ресурсы

Научиться

Получить продукты и технологии

Обсудить

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

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=422869
ArticleTitle=Создание сценариев для редактора Vim: Часть 1. Переменные, значения и выражения
publish-date=08252009