Vim по полной: Snippets и шаблоны файлов
- Введение (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 и я предлагаю их попробовать. Сниппеты это именованные отрывки кода (да чего угодно), которые можно быстро вставить введя имя сниппета и нажав «горячую клавишу». На пример вы хотите вставить в класс метод getter, который возвращает свойство login. С использованием сниппетов вам будет достаточно набрать слово get в месте, где будет располагаться метод, а затем нажать клавишу Tab. В результате будет вставлен шаблон getter метода, а указатель будет помещен в теле метода так, чтобы вы могли указать имя возвращаемого свойства.
Взгляните на пример. Указатель будет помещен на место символа нижнего подчеркивания (_). После ввода слова 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:
Как-то раз я обратил внимание на то, как много времени приходится тратить мне на создание документации для моих плагинов 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 и язык написания сниппетов, но это с лихвой окупится, когда вы начнете создавать целые проекты за несколько часов.