Оглавление
- Введение (vim_lib)
- Менеджер плагинов без фатальных недостатков (vim_lib, vim_plugmanager)
- Уровень проекта и файловая система (vim_prj, nerdtree)
- Snippets и шаблоны файлов (UltiSnips, vim_template)
- Компиляция и выполнение чего угодно (vim_start)
- Работа с Git (vim_git)
- Деплой (vim_deploy)
- Тестирование с помощью xUnit (vim_unittest)
- Библиотека, на которой все держится (vim_lib)
- Другие полезные плагины
Стоит ли рассказывать вам, как повторное использование кода и проектных решений облегчает жизнь программиста? Но все ли мы можем использовать повторно? Очень часто я сталкиваюсь в моих проектах с задачами, которые требуют копи-пасты кода и избежать этого невозможно. К категории этого «повторяемого» кода относятся все структуры используемого ЯП, многие классы проекта и тест-кейсы. К счастью давно изобретено решение, позволяющее работать с таким кодом быстрее и качественнее.
Уровни повторяющегося кода
Неизбежно повторяющийся код можно разделить по масштабу на две группы:
- Структуры ЯП или блоки кода — на пример структуры for, if/else, while, class, а так же готовые решения, которые невозможно не копи-пастить
- Целые файлы — на пример файлы модульных тестов, документация, классы сущностей
Для каждой группы используются различные решения, позволяющие копи-пастить код просто и быстро. Естественно эти решения уже реализованы в редакторе Vim и я предлагаю их попробовать.
Snippets
Сниппеты это именованные отрывки кода (да чего угодно), которые можно быстро вставить введя имя сниппета и нажав «горячую клавишу». На пример вы хотите вставить в класс метод 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, и вы получите готовый тег. Скажете, что сниппеты уже реализованы прямо в меню:
Но вы ведь используете 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, используемого в моем текущем проекте:
<?php
/**
* <++>
*
* @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, плагин заполнит его следующим образом:
<?php
/**
*
*
* @author Artur Sh. Mamedbekov
*/
class Db_ClientMapper extends My_Db_Mapper{
/**
* @see My_Db_Mapper::getDefaultTable
*/
public function getDefaultTable(){
$tableName = '_client';
$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 Db_Client;
}
}
Обратите внимание, что пространство имен для класса вычисляется автоматически. Остается только немного дополнить класс частными решениями и он готов к работе.
Пока все
Если вам приходится повторять одну и ту же структуру во время работы с Vim, попробуйте реализовать подходящий сниппет или шаблонный файл, это сэкономит вам уйму времени. Конечно, придется изучить VimLanguage и язык написания сниппетов, но это с лихвой окупится, когда вы начнете создавать целые проекты за несколько часов.
Автор: Delphinum