Emacs как IDE для web-разработки

В этом посте не будет вводных слов и примечаний: те, кто знает что собой представляют IDE и Emacs, но ещё не довели свой файл конфигурации до ума, возможно, найдут для себя что-то интересное. Те же, кто использует новомодные текстовые редакторы, скорее всего обойдут данную заметку стороной, хотя... ребят, постойте! Зачем сидеть на новом условно-бесплатном, если старые проверенные временем редакторы могут быть так же хороши? Ну, вы поняли: предположительно вторая категория возможных читателей это sublime'ры. Приступим же.

Основные пакеты

Это о тех пакетах, которые пригодятся всегда, вне зависимости от используемого языка программирования и личных пристрастий. Среди них имеются как уже встроенные, так и те, что ещё предстоит поставить. Прошу обратить внимание: конфигурация строится на основе .emacs.d от purcell и в ней для удобства определены некоторые нестандартные функции.

Напомню, что настройка осуществляется в вашем конфигурационном файле, таком как .emacs или же (что удобно когда настроек и пакетов множество) в файле .emacs.d/init.el, где подгружаются файлы, в каждом из которых описаны настройки только для его собственного пакета.

У автора структура каталога emacs выглядит следующим образом:

~/.emacs.d/
├── init.el
├── lisp
│   ├── init-css.el
│   ├── init-ruby-mode.el
├── snippets
│   ├── css-mode
└── themes
    └── railscasts-theme.el

В init.el подгружаются файлы из директории lisp. Каждый файл описывает какой пакет здесь необходимо загрузить и какие настройки для этого пакета использовать. Идея была украдена у человека с ником purcell, о чём уже упоминалось во вводной статье.

В «умолчательной» комплектации emacs содержит пакет ido, который позволит с лёгкостью перемещаться между открытыми буферами нажатием клавиш C-x b. Потребуем его загрузки при старте редактора и сообщим о необходимости использовать данный функционал сразу.

; init.el
(require 'ido)
(ido-mode t)
(require 'init-yasnippet)
(require-package 'autopair)
(autopair-global-mode)

Также хорош на вкус знаменитый YASnippet, поставляющий коллекцию сниппетов для самых разных языков программирования. Имеются горячие клавиши и возможность добавления сниппетов собственных. Впрочем, если есть необходимость интерактивной вставки (с параметрами), лучше написать свою функцию.

YASnippet

Настройки для YASnippet:

; init-yasnippet.el

(require-package 'yasnippet)
(yas-global-mode 1)

; где лежат сниппеты
(setq yas-snippet-dirs
      '("~/.emacs.d/snippets" ))

(provide 'init-yasnippet)

Самописные сниппеты должны располагаться в поддиректории, соответствующей имени включаемого режима. Это может быть prog-mode, ruby-mode, css-mode и прочие.

И ещё одна незаменимая вещь: autopair автоматически закрывает парные скобки и кавычки. Включение global-mode активирует такое поведение всюду, даже в plain text файлах.

Web-mode

Для примера настроим умное автодополнение в JavaScript и Ruby, позаботимся о закрытии HTML-тегов, подсветке всего и вся. Описание дано короткое: в случае возникновения желания разобраться подробнее следует обратиться к описанию того или иного пакета, которое быстро ищется при помощи google. Большинство пакетов также лежит (и ищется) на github.com.

Вариантов настройки может быть несколько: автором предлагаются лишь некоторые из них. Прежде всего, это включение таких пакетов как web-mode (автозакрытие парных HTML-тегов, подсветка js, html, erb), css-mode (встроен изначально, дополнительно к нему можно настроить поддержку sass и less).

;;; Web-mode
(require-package 'web-mode)

; с какими файлами ассоциировать web-mode
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))

; настройка отступов
(setq web-mode-markup-indent-offset 2)
(setq web-mode-css-indent-offset 2)
(setq web-mode-code-indent-offset 2)

; сниппеты и автозакрытие парных скобок
(setq web-mode-extra-snippets '(("erb" . (("name" . ("beg" . "end"))))
                                ))
(setq web-mode-extra-auto-pairs '(("erb" . (("open" "close")))
                                  ))

; подсвечивать текущий элемент
(setq web-mode-enable-current-element-highlight t)

Данный пакет незаменим в случае, если вам приходится работать с разного рода шаблонизаторами да и просто чистым HTML. А ежели ещё применить чуток фантазии и написать несколько ya-сниппетов... в общем, обязательно попробуйте.

JavaScript

А что насчёт JavaScript?

;;; JS

(require-package 'json-mode)
(require-package 'js2-mode)
(require-package 'ac-js2)
(require-package 'coffee-mode)

(require-package 'tern)
(require-package 'tern-auto-complete)

(add-hook 'js-mode-hook (lambda () (tern-mode t)))
(eval-after-load 'tern
   '(progn
      (require 'tern-auto-complete)
      (tern-ac-setup)))

Как уже было упомянуто выше, для тонкой настройки рекомендуется обратиться к документации пакета. Здесь же хочется поставить акцент на последнем пакете: tern. Tern обеспечивает автодополнение методов JavaScript, но для своей работы требует предварительной установки node.js с пакетным менеджером npm:

# emerge -pav net-libs/nodejs
# npm install -g tern

После проведения этой процедуры можно активировать tern-mode

Tern

Весьма удобно, не так ли?

Ruby

Здесь следует сесть и подумать что приходит на ум при слове «ruby». Пожалуй, это такие вещи как сам интерпретатор, подсветка синтаксиса и автодополнение методов подобно тому, как мы реализовали её в JavaScript тоже пригодилась бы, ещё rvm с возможностью переключаться между используемыми гемами, rails, тестирование кода с rspec... много всего. Что ж, давайте по-порядку.

Перво-наперво надо бы активировать сам ruby-mode и связать его с некоторыми часто используемыми файлами, которые не имеют расширения .rb.

(require-package 'ruby-mode)
(require-package 'ruby-hash-syntax)
(add-auto-mode 'ruby-mode
    "Rakefile\\'" "\\.rake\\'" "\\.rxml\\'"
    "\\.rjs\\'" "\\.irbrc\\'" "\\.pryrc\\'" "\\.builder\\'" "\\.ru\\'"
    "\\.gemspec\\'" "Gemfile\\'")

Для тех, кто использует rvm, неплохо было бы позаботиться о возможности интеграции оного в emacs, чем мы, собственно, и займёмся.

(require-package 'rvm)
(rvm-use-default)
; для связки rvm+robe
(defadvice inf-ruby-console-auto (before activate-rvm-for-robe activate)
  (rvm-activate-corresponding-ruby))

Emacs умеет запускать терминал, что избавляет от необходимости постоянно переключаться между окнами. Но как запустить irb, не открывая юниксовый shell? Для этого существует inf-ruby.

(unless (package-installed-p 'inf-ruby)
  (package-install 'inf-ruby))

(autoload 'inf-ruby "inf-ruby" "Run an inferior Ruby process" t)
(add-hook 'ruby-mode-hook  'inf-ruby-minor-mode)

Часто (если не сказать всегда) ruby ассоциируют с его «киллер-фичей»: фреймворком Ruby On Rails. И здесь emacs'еров ждёт много замечательных вещей.

Начнём с подключения пакета rinari, который поможет быстро перемещаться между каталогами проекта, запускать встроенный веб-сервер и собственную консоль рельсов.

(require-package 'rinari)
(after-load 'rinari
  (diminish 'rinari-minor-mode "Rin"))
(global-rinari-mode)

(defun update-rails-ctags ()
  (interactive)
  (let ((default-directory (or (rinari-root) default-directory)))
    (shell-command (concat "ctags -a -e -f " rinari-tags-file-name " --tag-relative -R app lib vendor test"))))

Что получим на выходе:

rinari

Но этого мало! Мало, потому что желательно иметь возможность запускать тесты. Да, прямо в emacs. И rspec-mode будет здесь хорошим помощником. С ним можно запустить как полный набор тестов, так и протестировать единственный spec-файл.

(require-package 'rspec-mode)
(eval-after-load 'rspec-mode
 '(rspec-install-snippets))

О том, как выбрать нужное, речь пойдёт чуть позже.

Небесполезная штука: подсветка блоков. Иногда это может оказать весомую помощь при работе с большим (очень большим) файлом. С задачей справляется ruby-blocks, хотя он имеет недостаток: при загрузке стандартным методом (elpa) пакетик куражится. Поэтому предлагается взять соответствующий файл у его автора и поместить в lisp/init-ruby-block.el.

; init.el
(require 'init-ruby-block)

(ruby-block-mode t)
(setq ruby-block-highlight-toggle t)

Наконец, вспомним об автодополнении для чего будем использовать robe. Robe не будет работать без активированного inf-ruby, иначе говоря без запущенного процесса ruby также как tern без установленного nodejs.

Поэтому есть два пути: последовательно включить соответствующие режимы или попробовать автоматизировать это дело. С последним при использовании rvm (без установленного в /usr/bin ruby) у автора возникли временные сложности, коих не должно наблюдаться в случае, если руби установлен в директорию по-умолчанию.

(require-package 'robe)

; для работы с rvm
(defadvice inf-ruby-console-auto (before activate-rvm-for-robe activate)
   (rvm-activate-corresponding-ruby))

(after-load 'ruby-mode
  (add-hook 'ruby-mode-hook 'robe-mode))
(after-load 'robe
  (add-hook 'robe-mode-hook 'ac-robe-setup))
  (add-hook 'robe-mode-hook
            (lambda ()
              (add-to-list 'ac-sources 'ac-source-robe)
              (set-auto-complete-as-completion-at-point-function)))

Далее предлагается лицезреть достаточно большой кусок кода, благодаря которому «автозакрываются» блоки в erb-файлах, а также происходит проверка на ошибки.

(require-package 'mmm-mode)
(defun sanityinc/ensure-mmm-erb-loaded ()
  (require 'mmm-erb))

(require 'derived)

(defun sanityinc/set-up-mode-for-erb (mode)
  (add-hook (derived-mode-hook-name mode) 'sanityinc/ensure-mmm-erb-loaded)
  (mmm-add-mode-ext-class mode "\\.erb\\'" 'erb))

(let ((html-erb-modes '(html-mode html-erb-mode nxml-mode)))
  (dolist (mode html-erb-modes)
    (sanityinc/set-up-mode-for-erb mode)
    (mmm-add-mode-ext-class mode "\\.r?html\\(\\.erb\\)?\\'" 'html-js)
    (mmm-add-mode-ext-class mode "\\.r?html\\(\\.erb\\)?\\'" 'html-css)))

(mmm-add-mode-ext-class 'html-erb-mode "\\.jst\\.ejs\\'" 'ejs)

(add-auto-mode 'html-erb-mode "\\.rhtml\\'" "\\.html\\.erb\\'")
(add-to-list 'auto-mode-alist '("\\.jst\\.ejs\\'"  . html-erb-mode))
(mmm-add-mode-ext-class 'yaml-mode "\\.yaml\\'" 'erb)

(dolist (mode (list 'js-mode 'js2-mode 'js3-mode))
  (mmm-add-mode-ext-class mode "\\.js\\.erb\\'" 'erb))

Навигация по проекту

В заключение хотелось бы порекомендовать projectile.

(require 'init-projectile)

Этот пакет упростит навигацию по файлам и директориям отдельного проекта. Имеется специальная версия для RoR: projectile-rails. Думается, при наличии одного проекта можно обойтись без подобных излишеств. А вот если проектов множество, и в каждом одинаковая структура, и между ними нужно переключаться! Без projectile одна только навигация может быть аду подобна.

Помимо вышеперечисленного рекомендуется также обзавестить поддержкой emmet, поставить sr-speedbar и... написать пару-тройку сниппетов.

И, как обещалось ранее, примеры доступных нам полезных команд.

описаниекоманда
сравнить содержимое буфера и оригинального файлаdiff-buffer-with-file
проверить грамотность автораflyspell-buffer
закомментировать выделенную областьcomment-region
раскоментировать её жеuncomment-region
включить режим web-modeweb-mode
включить режим tern-modetern-mode
запустить ruby shellinf-ruby
запустить roberobe-start
прогнать текущий тестrspec-verify
запустить весь набор тестовrspec-verify-all
запустить консоль railsrinari-console
найти файл в проекте при помощи rinaririnari-find-file-in-project
выбрать версию ruby и gemsetrvm-use

Сегодня это всё. Главная мысль: vim и emacs не уступают многим графическим платным/условно-работоспособным инструментам, а в некоторых ситуациях превосходят их.