Pull to refresh

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

Reading time 7 min
Views 17K

Оглавление


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

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

Уровни повторяющегося кода


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

Для каждой группы используются различные решения, позволяющие копи-пастить код просто и быстро. Естественно эти решения уже реализованы в редакторе 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 и язык написания сниппетов, но это с лихвой окупится, когда вы начнете создавать целые проекты за несколько часов.
Tags:
Hubs:
+15
Comments 6
Comments Comments 6

Articles