Vim по полной: Snippets и шаблоны файлов

  1. Введение (vim_lib)
  2. Менеджер плагинов без фатальных недостатков (vim_lib, vim_plugmanager)
  3. Уровень проекта и файловая система (vim_prj, nerdtree)
  4. Snippets и шаблоны файлов (UltiSnips, vim_template)
  5. Компиляция и выполнение чего угодно (vim_start)
  6. Работа с Git (vim_git)
  7. Деплой (vim_deploy)
  8. Тестирование с помощью xUnit (vim_unittest)
  9. Библиотека, на которой все держится (vim_lib)
  10. Другие полезные плагины

Стоит ли рассказывать вам, как повторное использование кода и проектных решений облегчает жизнь программиста? Но все ли мы можем использовать повторно? Очень часто я сталкиваюсь в моих проектах с задачами, которые требуют копи-пасты кода и избежать этого невозможно. К категории этого «повторяемого» кода относятся все структуры используемого ЯП, многие классы проекта и тест-кейсы. К счастью давно изобретено решение, позволяющее работать с таким кодом быстрее и качественнее.Неизбежно повторяющийся код можно разделить по масштабу на две группы:

  1. Структуры ЯП или блоки кода — на пример структуры for, if/else, while, class, а так же готовые решения, которые невозможно не копи-пастить
  2. Целые файлы — на пример файлы модульных тестов, документация, классы сущностей

Для каждой группы используются различные решения, позволяющие копи-пастить код просто и быстро. Естественно эти решения уже реализованы в редакторе Vim и я предлагаю их попробовать. Сниппеты это именованные отрывки кода (да чего угодно), которые можно быстро вставить введя имя сниппета и нажав «горячую клавишу». На пример вы хотите вставить в класс метод getter, который возвращает свойство login. С использованием сниппетов вам будет достаточно набрать слово get в месте, где будет располагаться метод, а затем нажать клавишу Tab. В результате будет вставлен шаблон getter метода, а указатель будет помещен в теле метода так, чтобы вы могли указать имя возвращаемого свойства.

Пример
public function get (){ return $this→_; }

Взгляните на пример. Указатель будет помещен на место символа нижнего подчеркивания (_). После ввода слова login, имя метода будет изменено автоматически.

Пример
public function getLogin (){ return $this→login; }

Удобно, не правда ли? А ведь плагин UltiSnips позволяет реализовывать шаблоны для задач любой сложности, будь то структуры языка или целые классы. Я уже довольно давно использую этот плагин для реализации сниппетов в Vim.

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

assert ('is_int (_) && _ > 0'); То есть проверка входного параметра на принадлежность к типу int и значению больше нуля.

Другим примером являются мои сниппеты для Хабра. Вы знали, что с помощью плагина для Firefox, который называется Vimperator, можно писать статьи на Хабр прямо из Vim? Для этого достаточно открыть окно редактирования статьи и поместив указатель в textarea нажать комбинацию Ctrl+i. После этого откроется редактор Vim и все что вы в нем напишите после сохранения (: wq) будет скопировано в этот textarea. Круто? А как на счет использования сниппетов для вставки html-тегов? Так, для добавления habracut достаточно набрать cut и нажать Tab, и вы получите готовый тег. Скажете, что сниппеты уже реализованы прямо в меню: 15fa482277454f33b943d3bd81a7247c.pngНо вы ведь используете Vim, а значит компьютерная мышь для вас враг номер один!

Вы полюбите сниппеты, если вам приходится писать на различных ЯП. В этом случае вам не придется запоминать, как именно пишутся те или иные структуры в конкретном языке, а достаточно реализовать сниппеты со схожими именами. На пример, я всегда забываю как реализуются те или иные структуры на Bash, потому я просто использую такие сниппеты, как if, for, foreach и т.д.

Я не хочу описывать в этой статье как писать сниппеты под UltiSnips, так как официальная документация сделает это намного лучше меня, приведу только небольшой пример объявления сниппета для создания метода getter:

Пример
snippet get «public function get …» b /** * $2. * @return ${3: mixed} */ public function get${1/\w+\s*/\u$0/}(){ return $this→$1; }$0 endsnippet

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

Пример

имяФайла.txt Для Vim версии 7.0. Имя плагина РУКОВОДСТВО ПО `Имя плагина`

1. Описание имяПлагина-description 2. Зависимости имяПлагина-requirements 3. Установка имяПлагина-install 4. Использование имяПлагина-use 5. Опции имяПлагина-opt 6. Команды имяПлагина-commands 7. Меню имяПлагина-menu 8. События имяПлагина-events

================================================================================ 1. Описание имяПлагина-description

Описание плагина …

================================================================================ 2. Зависимости имяПлагина-requirements

Данный плагин работает с редактором Vim версии 7.0 или старше.

vim_lib https://github.com/Bashka/vim_lib Плагин реализован с использованием класса vim_lib#sys#Plugin#, а так же использует некоторые компоненты этой библиотеки.

Обычно документация к плагину Vim включает около сотни строк, из которых около шестидесяти — это шаблонные данные, такие как шапка, оглавление и разделы. Использовать для создания документации сниппеты требовало бы от меня повторяющихся действий, чего мне не хотелось. Тогда я решил написать плагин vim_template. Этот плагин заполняет пустой файл некоторыми данными, создавая шаблон и лишая меня необходимости повторять однотипные операции для подготовки файла к работе. Шаблоны файлов можно очень гибко настроить с помощью VimLanguage, что позволяет создавать файлы с очень сложной структурой (на пример автоматически добавлять namespace в начало файла с учетом расположения класса в файловой системе). Другой особенностью плагина, является возможность определить контекст шаблона. На пример, можно создать шаблон только для файлов тест-кейсов или для файлов, расположенных в каталоге ~/.vim/bundle/, а загрузка уровня проекта, о которой я уже говорил в прошлых статьях, позволяет определить шаблоны только для конкретного проекта.

Плагин vim_template устроен довольно просто. При открытии некоторого файла, он последовательно ищет для него файл-шаблон (в каталогах ./.vim/templates, ~/.vim/templates и $VIMRUNTIME/templates), содержимое которого будет скомпилировано и вставлено в этот файл. Логика поиска файла-шаблона позволяет не только отталкиваться от имени файла, но и учитывать расположение его в файловой системе. Вот несколько примеров:

  • Если есть шаблон ___.php, то он будет применяться ко всем файлам с данным расширением
  • Если есть шаблон ___Test.php, то он будет применяться ко всем тест-кейсам для PHP классов перекрывая предыдущий
  • Если есть шаблон autoload/___.vim, то он будет применяться ко всем файлам с расширением vim, которые расположены в каталоге autoload
  • Если шаблон расположен относится к проекту (расположен в каталоге ./.vim/templates), то он будет использоваться только в этом проекте

Удобно и гибко, не правда ли? Вот несколько примеров из реальных проектов:

  • Шаблоны для документации плагинов Vim
  • Шаблоны для файлов плагинов Vim, расположенных в каталоге plugin и autoload (у них обычно схожие структуры)
  • Шаблоны для тест-кейсов
  • Шаблоны для сущностей и Mapper’ов

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

  • Информация об авторе, лицензии, дате создания
  • Имя класса, получаемое из имени файла
  • namespace класса, получаемый из расположения файла в файловой системе
  • Константа, значение которой вычисляется на основании имени файла или расположения в файловой системе

Делается все это с помощью специальных маркеров, которые заменяются на значения во время вставки шаблона (они вставляются в шаблон в виде следующей записи <+имя+>). Эти маркеры могут быть перечислены в словарях vim_template#keywords и vim_prj#opt. Лично я использую такие маркеры, как: author, email, license и т.д. Помимо перечисляемых вами маркеров, доступны так же предопределенные:

  • date — текущая дата
  • time — текущее время
  • datetime — дата и время
  • file — имя текущего файла
  • ftype — расширение текущего файла
  • fname — имя текущего файла без расширения
  • dir — адрес каталога в котором расположен текущий файл, относительно корня проекта
  • namespace — адрес текущего файла относительно корня проекта

Но на предопределенных маркерах далеко не уедешь, потому возможно использование «исполняемых маркеров» (они заключены в косые кавычки). Это блоки кода на языке VimLanguage, которые будут исполнены при вставке шаблона. С их помощью можно преобразовывать маркеры (на пример превратить маркер dir в пространство имен текущего класса заменив символ слеша на точку), вычислять новые маркеры и т.д. Все стандартные маркеры здесь доступны в виде локальных переменных Vim (l: date, l: dir, l: file и т.д.).

В качестве примера приведу шаблон для класса Mapper, используемого в моем текущем проекте:

Пример

* * @author <+author+> */ class `substitute (strpart (l: dir, strlen ('application/')), '/', '_', 'g')`_<+fname+> extends My_Db_Mapper{ /** * @see My_Db_Mapper: getDefaultTable */ public function getDefaultTable (){ $tableName = '`tolower (substitute (strpart (l: dir, strlen («application/db/»)),»/»,»_», «g») .»_» . strpart (l: fname, 0, strlen (l: fname) — strlen («Mapper»)))`'; $table = new My_Db_Table ([ 'name' => $tableName, ]); $table→_linkedCacheTags = [$tableName];

return $table; }

/** * @see My_Db_Mapper: getStateEntity */ protected function getStateEntity (\My_Db_Entity $entity){ return [ '' => $entity→(), ]; }

/** * @see My_Db_Mapper: setStateEntity */ protected function setStateEntity (array $state, \My_Db_Entity $entity){ $entity→($state['']); }

/** * @see My_Db_Mapper: getEmptyEntity */ protected function getEmptyEntity (){ return new `substitute (strpart (l: dir, strlen ('application/')), '/', '_', 'g')`_`strpart (l: fname, 0, strlen (l: fname) — strlen ('Mapper'))`; } }

При создании нового файла, на пример ClientMapper.php, плагин заполнит его следующим образом:

Пример

$tableName, ]); $table→_linkedCacheTags = [$tableName];

return $table; }

/** * @see My_Db_Mapper: getStateEntity */ protected function getStateEntity (\My_Db_Entity $entity){ return [ '' => $entity→(), ]; }

/** * @see My_Db_Mapper: setStateEntity */ protected function setStateEntity (array $state, \My_Db_Entity $entity){ $entity→($state['']); }

/** * @see My_Db_Mapper: getEmptyEntity */ protected function getEmptyEntity (){ return new Db_Client; } }

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

© Habrahabr.ru