Программисты делятся на две категории:
1) Те, кто уже использует Vim.
2) Те, кто уже использует Emacs.
3) Те, кто ещё не использует.
Предисловие
Как-то пришла идея поставить Emacs во второй раз, чтобы ещё раз убедиться, что это какой-то неправильный редактор с кучей разных игр, но никак не функций для работы с текстом. Так и остался на нём.
Добавление режима
В Emacs'е есть множество разных режимов, добавляющих функциональность в него. Как правило, когда нужна какая-то фича, она скачивается в виде пакета, состоящего из файлов .el (Emacs Lisp), и они уже подключаются к встроенным .el файлам, отвечающим за загрузку редактора.
Сначала это всё удобно использовать, но потом начинает чего-то не хватать и приходится думать о добавлении своей функциональности.
Когда тебе нужно добавить одно клавиатурное сочетание, ты можешь это сделать напрямую в каком-нибудь из файлов настройки. Поначалу это работает и приносит сплошную радость, но в скором времени это начинает работать не так, как ожидалось: одни клавиши залазят на другие, другие — на первые, третьи — на вторые. Приходится всё переделывать или от чего-то отказываться.
Чем дольше работаешь с Emacs'ом, тем больше он нравится. Поэтому мотивация не заставила себя долго ждать — появилась задача: «Мне нужно иметь какую-то коробочку с инструментами для автоматического форматирования ответов на форум.»
Как решить (естественно, не выключая Emacs)?
Ответ прост: нужно добавить свой режим, который можно включить, когда нужен, и отключить в обратном случае.
Обратная инженерия
Естественно, для добавления режима нужно прочитать кучу документации, посмотреть, как сделаны другие режимы, изучить лисп и случайно не запортачить что-нибудь в работающем Emacs'е.
Начались поиски документации. Сначала туториальные статьи… статей нет. Тогда примеры режимов… примеры переполнены лишними конструкциями. Тогда документация… это читать и проверять несколько дней. Тогда готовые режимы… ничего не понятно, надо учить лисп.
Круг замкнулся. Пришлось использовать всё сразу и много.
Первое приближение
Информация:
1) Режимы бывают главные (major) и побочные (minor).
2) Режимы могут наследоваться от существующих режимов.
3) Главный режим может быть выбран только один, тогда как побочных — много.
4) К главному режиму можно прицепить множество побочных.
Сперва был составлен самый минимальный режим:
Файл test-mode.el с этим текстом кладётся в удобную папку, а затем режим подключается где-нибудь в файле инициализации, как и любой другой.
;; My test mode
(define-derived-mode test-mode python-mode "Test"
"Major mode for editing Test source text."
(set (make-local-variable 'test-variable) "Test variable value"))
(add-to-list 'auto-mode-alist (cons "\\.test\\'" 'test-mode))
(provide 'test-mode)
Файл test-mode.el с этим текстом кладётся в удобную папку, а затем режим подключается где-нибудь в файле инициализации, как и любой другой.
(add-to-list 'load-path "~/.emacs.d/packages/modes/test-mode/")
(require 'test-mode)
Объяснение содержимого:
Создаём режим, наследующий всё из питоновского режима:
Эта строка будет показываться в полоске названия режима:
Это комментарий, который показывается, когда открываешь помощь для режима:
Это переменная, которая создаётся при входе в режим и разрушается при выходе из него:
Это назначает файлам с расширением .test этот режим автоматически:
Это расшаривает режим, чтобы его можно было подключить из других файлов:
(define-derived-mode test-mode python-mode
Эта строка будет показываться в полоске названия режима:
"Test"
Это комментарий, который показывается, когда открываешь помощь для режима:
"Major mode for editing Test source text."
Это переменная, которая создаётся при входе в режим и разрушается при выходе из него:
(set (make-local-variable 'test-variable) "Test variable value"))
Это назначает файлам с расширением .test этот режим автоматически:
(add-to-list 'auto-mode-alist (cons "\\.test\\'" 'test-mode))
Это расшаривает режим, чтобы его можно было подключить из других файлов:
(provide 'test-mode)
С минимумом закончили.
Как этим всем пользоваться?
Ну, мы можем отнаследовать, а потом любую деталь переопределить. К примеру, одно из клавиатурных сочетаний подменить на другое, либо синтаксическое слово, которое раскрашивается в один цвет, перекрасить в другой.
Добавляем функцию и назначаем клавишу для неё:
;; My test function mode
(defun test-func-hello()
(interactive)
(message "Pressed C-c 1, test-func-hello()"))
(defvar test-func-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c 1") 'test-func-hello)
map)
"Keymap for `test-func-mode'.")
(define-derived-mode test-func-mode python-mode "TestFunc"
"Major mode for editing TestFunc source text."
(set (make-local-variable 'test-func-variable) "TestFunc variable value"))
(add-to-list 'auto-mode-alist (cons "\\.testfunc\\'" 'test-func-mode))
(provide 'test-func-mode)
Здесь изменено только имя режима и расширение файлов, в которых он будет включаться автоматически. И ещё добавлено две штуки: функция и отображение клавиш.
Когда мы создаём файл file.testfunc и открываем его в Emacs'е, в нём устанавливается режим TestFunc. Так как режим унаследован от питоновского, у нас работает подсветка синтаксиса и другие вещи. Но при нажатии Ctrl + c + 1 у нас высвечивается сообщение о том, что нажата эта комбинация, и имя сработавшей функции.
Теперь мы можем создать директорию режима в директории yasnippet с именем нашего режима test-mode или test-func-mode и положить туда какие-нибудь снипеты, которые будут отделены от обычных питоновских и даже при перекрытии будут лишь дополнять друг друга.
Минимальное связывание
Спустя какое-то время понимаешь, что это всё хорошо, конечно, но когда сидишь в уже правильном режиме, нужно в нём и оставаться. А текущий режим со своими фишками может не совпасть с тем, который наследовался изначально.
Поэтому для подмешивания функциональности в текущий режим на помощь приходит побочный режим (малый или минорный). У него есть всё то же самое, что и у главного режима, но он не заставляет текущий режим бросать обрабатываемые данные и срочно куда-то переключаться.
Переделываем главный режим с функцией в побочный:
;; My test minor mode
(defun test-minor-hello()
(interactive)
(message "Pressed C-c 1, test-minor-hello()"))
(defvar test-minor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c 1") 'test-minor-hello)
map)
"Keymap for `test-minor-mode'.")
(define-minor-mode test-minor-mode
"Minor mode for editing TestMinor text."
nil
" TestMinor"
nil
(if test-minor-mode
(set (make-local-variable 'test-minor-variable)
"TestMinor variable value")
(makunbound 'test-minor-variable)))
(add-to-list 'auto-mode-alist (cons "\\.testminor\\'" 'test-minor-mode))
(provide 'test-minor-mode)
Как видно, здесь всё не так радостно, как при создании главного режима. Появились какие-то странные конструкции, совсем не понятные.
Если объяснять по-простому, побочный режим представляет из себя переключаемую конструкцию. Его можно включить или выключить, поэтому он должен уметь определять своё состояние, чтобы не включаться повторно, когда он уже включен. К тому же в отличие от главного режима он не чистит за собой свои переменные, поэтому требуется иметь такую ветвь, которая срабатывает при отключении.
Если объяснять по-сложному, в Emacs'е зашито определённое поведение для работы с такими режимами. Можно было их сделать лучше, но уже поздно.
Главное, что нужно помнить про малые режимы, — у них вверху четыре переменные. Там можно случайно это упустить, и он выражение, которое должно выполняться при включении режима, подставит вместо последней переменной, которая никак об этом не сообщит — будешь два часа сидеть в документации, чтобы понять, почему не работает выражение.
Решение поставленной задачи
Конечный вариант
;; Forum minor mode
(defun forum-minor-wrap-code()
(interactive)
(if (not (mark))
(set-mark (point)))
(narrow-to-region (mark) (point))
(goto-char (point-min))
(insert "<code>")
(goto-char (point-max))
(insert "</code>")
(set-mark (point-min))
(widen))
(defun forum-minor-wrap-quote()
(interactive)
(if (not (mark))
(set-mark (point)))
(narrow-to-region (mark) (point))
(goto-char (point-min))
(insert "<quote>")
(goto-char (point-max))
(insert "</quote>")
(set-mark (point-min))
(widen))
(defvar forum-minor-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "C-c 1") 'forum-minor-wrap-quote)
(define-key map (kbd "C-c 2") 'forum-minor-wrap-code)
map)
"Keymap for `forum-minor-mode'.")
(define-minor-mode forum-minor-mode
"Minor mode for editing text for `http://forum.example.ru'."
nil
" Forum"
nil
(if forum-minor-mode
(set (make-local-variable 'forum-minor-nick)
"Nick")
(makunbound 'forum-minor-nick)))
(add-to-list 'auto-mode-alist (cons "\\.forum\\'" 'forum-minor-mode))
(add-to-list 'yas-extra-modes 'forum-minor-mode)
(provide 'forum-minor-mode)
Здесь добавлена пара функций для оборачивания в теги <quote></quote> и <code></code> выделенного текста. К тому же пришлось добавлять режим в yasnippet, потому что тот обнаруживает только главные режимы.
Здесь нужно отметить, что режим можно привязать к файлу, запустить вручную через Alt + x + название, либо прицепить к другому режиму через хук.
Вот так выглядит присоединение к питоновскому:
(add-hook 'python-mode-hook 'forum-minor-mode)
Теперь можно заняться написанием более продвинутых функций по обработке текста. Режимы можно добавлять и удалять, не влияя на остальные настройки.
Заключение
Когда я поставил Emacs в первый раз, то не понял его, он показался мне сложным, неудобным и некрасивым (из-за древних шрифтов). Теперь же, поставив его во второй раз, я его не узнал. В нём оказалось так много чудесных приложений, а удобство зашкаливает.
До него я пользовался Vim'ом, это было хорошо и интересно, но его настройки казались какими-то неродными, всё время нужно было читать что-то, чтобы понять мнемонику. Из-за этого потом долго программировал в kwrite, потому что изучать ничего не надо, а синтаксис выравнивает и подкрашивает хорошо.
Но только установив и влившись в Emacs, я понял, что такое мощь и простота (до поры до времени, конечно, пока не попробуешь открыть файл на 20Мb).
P.S.: Если что неправильно во мною приведённом коде или подходе — велком.
Комментарии (7)
MAXH0
13.10.2015 18:49Работаю в emacs преимущественно в консольном режиме (проклятая ностальгия).
Единственно что напрягает — костыли с проверкой орфографии. Я понимаю что это не основное применение этого редактора, но все же по умолчанию — все довольно неудобно, а пилить нет времени и сил.samo-delkin
14.10.2015 04:44+1Работаю в emacs преимущественно в консольном режиме (проклятая ностальгия).
Я в git'е его юзаю в консоли
.gitconfig
[core] editor = emacs --no-window-system
поставил его консольным, потому что оконный медленно открывается.
Я понимаю что это не основное применение этого редактора, но все же по умолчанию — все довольно неудобно
Я, наоборот, ставил себе целью изучить умолчания, поэтому проигнорировал и переделывание клавиш, и стартеркиты.
Просто попадёшь на комп с линем, где есть Emacs, но нет никаких настроек, и будешь связан по рукам и ногам.roman_kashitsyn
14.10.2015 11:04+1Я в git'е его юзаю в консоли
Если взять привычку запускать Emacs в режиме сервера, то можно сделать git-редактором emacsclient, он стартует очень быстро.
Ещё можно поставить magit и (практически) не переключаться на консольный гит. Magit во многом удобнее родного интерфейса git (да, я сам когда-то не подозревал, что такое возможно).samo-delkin
14.10.2015 13:40Если взять привычку запускать Emacs в режиме сервера, то можно сделать git-редактором emacsclient, он стартует очень быстро.
Когда ставил, рассматривал этот вариант, не в плане git'а, а вообще. Тоже не стал юзать.
Очень часто держу несколько открытых Emacs'ов, в каждом по множеству окон, всё изолировано.
Ещё можно поставить magit и (практически) не переключаться на консольный гит.
Я ещё не изучал системы контроля версий в Emacs'е, на видео сейчас глянул magit.
Сейчас у меня gitk (красивое дерево коммитов со всей инфой) и множество самодельных алиасов.
[alias] co = checkout ci = commit st = status cf = config --global br = branch mg = merge sh = stash rs = reset fp = format-patch ar = archive ds = describe sl = shortlog rl = reflog chp = cherry-pick bs = bisect sm = submodule fb = filter-branch logt = log --oneline --graph --decorate --all logp = log -p logfmt = log --pretty=format:\"%h %cr %cn <%ce>%n%s%n\" logd = log --relative-date logo = log --oneline logo1 = log --oneline -n 1 logolr = log --oneline --left-right logs = log --stat logsp = log -p --stat logps = log -p --stat log1 = log -1 log1p = log -p -1 logp1 = log -p -1 logg = log -g diffc = diff --cached diffh = diff HEAD diffk = diff --check diffck = diff --cached --check cim = commit -m cia = commit --amend cic = commit --reset-author -c cob = checkout -b cof = checkout -f brv = branch -vv brva = branch -avv brd = branch -d brdf = branch -D brm = branch -m mgb = merge --no-ff addp = add -p addi = add -i rmv = remote -v rms = remote show rmsu = remote set-url tagd = tag -d tagn = tag -n vi = !gitk --all vib = !gitk shl = stash list sha = stash apply shai = stash apply --index shp = stash pop shpi = stash pop --index shd = stash drop shsp = stash show -p shb = stash branch unstash = !git stash show -p | git apply -R dst = describe --tags rmc = rm --cached bsb = bisect bad bsg = bisect good bss = bisect start bsr = bisect run bsrs = bisect reset fbf = filter-branch -f sma = submodule add smui = submodule update --init smu = submodule update cfu = config --global --unset reco = "!git reset -q; git co *"
И вот это всё надо чтобы там работало, потому что я к этому привык. А с magit'ом, походу, придётся там изучать много нового, а потом ещё что-нибудь невозможно будет воспроизвести.
Интерес есть, но работа пока в таком режиме, когда всё привычно.roman_kashitsyn
15.10.2015 16:48А на этот случай у меня
проезднойM-x shell
:)
Я использую magit для большинства задач, изредка переключаясь в*shell*
, чтобы выполнить то, что не умеет magit.
Joric
cyber_genius
нда, и боюсь emacs такой же древний как и троичная система, но надо всё равно его попробовать