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

    Оглавление


    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 и язык написания сниппетов, но это с лихвой окупится, когда вы начнете создавать целые проекты за несколько часов.
    • +15
    • 13.1k
    • 6
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 6

      0
      Вы полюбите сниппеты, если вам приходится писать на различных ЯП.
      Сам так делаю, плюсую за это. Правда стандартизировать так можно только очень основные вещи в разных языках. В этом плане для меня go как основа ultisnips файла, а дальше уже дописываю language-related сниппеты — тут его простота блистает.

      Кстати, у Дрю Нейла с vimcasts аж три последних эпизода по ultisnips, советую посмотреть.
        0
        Как же бесит писать постоянно шаблоны классов с нуля, это одна из причин, почему я не перешёл полностью на вим с phpstorm
          0
          В PHPStorm есть шаблоны и сниппеты, правда не настолько гибкие как в Vim (или может я что то не так делал?).
            0
            Да в PHPStorm шаблоны и сниппеты есть и они меня устраивают, поэтому на нем и сижу, просто когда пилю что-то в Vim, то приходится все самому писать. А разобраться есть ли шаблоны подобные пхпсторму в виме все руки не доходили, а тут в статейке, как-то просто и лаконично вроде. Надо будет попробовать.
          0
          А в vim есть какие-нибудь приблуды для создания временных сниппетов?
          Например, я в emacs иногда создаю сниппет, который мне нужен только в данный момент времени и больше никогда не пригодится, локально только для конкретного буфера.
            0
            В этом случае я создаю сниппеты для проекта в ./.vim/UltiSnips. Как правило если сниппет написан, то он пригодится более 1 раза, да и жалко бывает удалять сниппет, пишется он не на раз-два обычно.

            У UltiSnips вроде можно создать сниппет только для текущего буфера, я не интересовался.

          Only users with full accounts can post comments. Log in, please.