Хорошо настроенный Emacs
Emacs [“Editor MACroS”, /ˈeditər ˈmakrōs/ → /ˈemaks/, далее — «эмакс»] был создан давным давно в Лаборатории искусственного интеллекта Массачусетского технологического института MIT AI Lab. Эмакс это текстовый редактор поведение и возможности которого изменяются прямо во время написания в нем текста на языке программирования на котором написан сам эмакс. Более того, прямо во время написания в эмаксе текста на языке программирования на котором написан сам эмакс изменяется поведение и возможности самого языка программирования на котором написан эмакс. Все это делает эмакс самым гибким и универсальным текстовым редактором в сравнении с любым другим.
Эмакс развивается уже несколько десятилетий. Каждый год сотни человек добавляют в него все новые и новые возможности. Обычно это небольшие фрагменты обособленной функциональности под нужды конкретного пользователя. Однако, общий объем и широта охвата этих улучшений со временем превратили мощный текстовый редактор во всеобъемлющую кроссплатформенную программную среду в которой можно делать абсолютно все что угодно, если это хотя бы немного относится к работе с текстом.
Универсальность эмакса играет вам на руку, если вы понимаете как он устроен: вы можете делать тысячу разных дел через единый интерфейс в котором все состоит из текста во всех его проявлениях. Возможно, это выглядит архаично с точки зрения представлений о современном пользовательском интерфейсе, но это не так; хотя бы потому что современные интерфейсы не раскрывают силу простого текста в полной мере. Чтобы стать грамотным пользователем эмакса, вам придется понять его концепцию и внутреннее устройство, а это значит — приобщиться к культуре хакеров 70-х годов прошлого века. Новичков такое положение дел пугает, но все не так плохо: эмакс сложен только для сложных задач, а для простых задач он прост, все зависит от ваших потребностей. Начать знакомство с ним лучше с общего обзора “A Guided Tour of Emacs” на сайте свободной операционной системы GNU.
Ричард Столлман описывает историю возникновения эмакса в статье «Мой опыт работы с лиспом и развитие GNU Emacs»; эта история тесно связана с не менее интересной историей проекта GNU из которой можно узнать, что GNU Emacs был первым проектом в проекте GNU. Суть того что из себя представляет эмакс подробно изложена в документе “EMACS: The Extensible, Customizable Display Editor” написанном в 1981 году — и за прошедшие годы эта суть ничуть не изменилась.
Расширяемость эмакса это его благословение и проклятье. Конечно, в стандартной поставке эмакс хорош, но не так хорош как мог бы быть. Вы не ограничены решениями разработчиков, и если вам нужна функциональность которой в эмаксе нет, вы можете добавить ее сами: достаточно просто открыть эмакс и подробно её описать. В конце концов все возможности эмакса по редактированию текста это всего лишь текст описывающий что делать с текстом, и вы можете изменить этот текст в любое время точно так же как и любой другой, тем самым изменив возможности эмакса. Потенциально его возможности безграничны, но чтобы ими воспользоваться придется взяться за настройку, а это подразумевает умение программировать и плотное общение с сообществом.
Настройка эмакса это своего рода декоративно-прикладное искусство; для каждого конкретного пользователя она заключается в составлении лисп-программы из фрагментов лисп-кода взятых из множества разрозненных источников. Никакого другого способа в принципе нет, поэтому перед погружением в эту тему следует освоиться с программированием вообще и программированием на лиспе в частности. Лисп это древнейший из используемых языков программирования очень высокого уровня, а также простейший из известных человечеству способов организации вычислений из символов, структур данных и функций (открытых математиками в 1936 году).
В изучении программирования поможет книга «Структура и интерпретация компьютерных программ» — это вводный курс по информатике в MIT. В деле написания программ для эмакса пригодится учебник «Введение в программирование на Emacs Lisp», справочник “GNU Emacs Lisp Reference Manual”, и брошюра “Common Lisp Quick Reference”.
Эмакс совершенно точно не та программа за изучение которой стоит браться только ради того, чтобы редактировать в нем текст. Его следует воспринимать скорее как живой артефакт и культурный памятник более цивилизованной эпохи. Изучение эмакса окажется пустой тратой времени если вы не программируете и не хотите иметь ничего общего с этой деятельностью, но если это не так, ничего лучше эмакса вы не найдете.
Обычно создание удобной для себя конфигурации и ее последующая доводка растягивается на долгое время. Можно облегчить себе жизнь и установить фреймворк в котором все основные настройки выполнены в соответствии с видением автора фреймворка, и вам даже не придется править исходный код для того чтобы просто пользоваться эмаксом в свое удовольствие. Самые популярные фреймворки на сегодня: Spacemacs, Emacs Prelude, Eschulte Literate Emacs Starter Kit, Overtone Emacs Live, Purcell emacs.d, Oh My Emacs.
Все будет хорошо до тех пор пока вы не захотите встроить в эмакс некую невероятную функциональность которой в выбранном фреймворке нет, и тогда вам придется вручную интегрировать некий код не только в эмакс, но и во фреймворк, а для этого нужно будет разобраться с его внутренним устройством, что сводит все его достоинства на нет. К такому выводу пришел автор Emacs Starter Kit (см. Meet Emacs), когда закрыл свой проект после шести лет развития:
Старые версии Emacs Starter Kit были единой-для-всех кодовой базой заменяющей содержимое директории ~/.emacs.d. Это было очень популярное решение, но большая связка бессвязной функциональности вела к тому, что пользователь просто привыкал к ней ничего не понимая. Когда некоторые вещи ломались или вели себя не так как вам хотелось, вы и понятия не имели где их исправить.
Я понял, что пользователям лучше подходят маленькие пакеты дающие желаемую функциональность. Вместо свалки кода, Emacs Starter Kit сделался небольшим руководством. Как пользователю Emacs, вам предстоит искать новые фрагменты эмакс-лисп-кода, интегрировать их, конфигурировать, и даже писать свои собственные. Emacs Starter Kit поможет вам советами где начать и что именно искать, но сборка годной конфигурации это личное дело каждого.
Система пакетов [инфо] эмакса это встроенная в него лисп-программа [см. исходник на github; открыть графический интерфейс системы пакетов в эмаксе — M-x list-packages] которая автоматически интегрирует в эмакс другие лисп-программы из интернета. Система пакетов работает независимо от операционной системы под которой запущен редактор, она сама отслеживает зависимости между лисп-программами и заботится об их обновлении. Разработку этой ситемы начал Том Тромей в 2007 году, в 2012 она была включена в состав Emacs 24.1; пик популярности Emacs Starter Kit пришелся именно на эти годы, то есть по большому счету сообщество использовало его для разработки системы пакетов.
Как бы там ни было, личная конфигурация эмакса по своей природе была есть и будет просто большой кучей бессвязаного кода, и лучший способ справиться с этим — оставить все как есть; или превратить эту кучу в руководство которое будет объяснять причины (в первую очередь чтобы не забыть самому) по которым тот или иной фрагмент находится в этой куче, а потом сделать так чтобы это руководство само превращало себя в лисп-программу и выполняло конфигурацию эмакса при запуске. А также пересобирало само себя при изменении и скачивало новые версии себя из удаленных источников. Почему бы и нет. Это же руководство для настройки эмакса мышью в лучших традициях Стэнфорда и MIT.
Превратить это руководство в лисп-программу инициализации эмакса .emacs или init.el (сплести программу — tangle) очень просто, достаточно скачать свежую версию этого руководства, открыть ее в эмаксе и выполнить команду M-x org-babel-tangle. В результате из фрагментов лисп-кода в этом руководстве будет создана лисп-программа инициализации эмакса [инфо, вики] которую эмакс будет автоматически выполнять при каждом запуске. В общем виде программа инициализации выглядит так:
<<header>> <<requirements>> <<customize-well-tuned-emacs>> <<customize-customize-and-apply-customizations>> <<try-to-download-this-reference-when-it-is-missing>> (if <<this-reference-is-in-its-place-and-is-newer-than-user-init-file>> <<tangle-this-reference-into-user-init-file-and-then-load-it-again>> (progn <<initialize-package-system-and-install-user-selected-packages>> <<load-packages-and-apply-advanced-customizations>> <<fix-some-bugs>> )) <<footer>>
Перед написанием кода на эмакс лиспе, следует немного обновить сам язык программирования. Эмакс лисп это древнейший из современных лиспов, созданный в темный период между возникновением лиспа как концепции теории вычислений, и первыми двумя классическими дизайнами лиспа: Common Lisp и Scheme; сама концепция лиспа так же стара для него, как он сам по отношению к современному Common Lisp'у, а современный Common Lisp в свою очередь — по отношению к современной Clojure. Эмакс лисп старомоден, его выразительные средства скудны, тем не менее, это полноценный лисп и он может с легкостью дополнить свои возможности основными языковыми средствами Common Lisp, просто выполнив относительно небольшую встроенную в эмакс лисп-программу GNU Emacs Common Lisp Emulation [инфо]. Стоит отметить, что сообщество очень неоднозначно относится к этой лисп-программе; например, Столлман выступает против ее включения в эмакс.
(require 'cl-lib)
В 24-й версии эмакса в эмакс-лиспе появилась поддержка лексической области видимости и замыканий (как в Scheme образца 1975 года). Лексическая область видимости делает программу инициализации более изящной, быстрой и надежной. Первая строка в файле инициализации устанавливает локальную переменную буфера и активирует соответствующую семантику языка программирования.
;;; Automatically generated by “Well Tuned Emacs” -*- lexical-binding: t -*-
Во время запуска эмакс выполняет
одну из девяти возможных программ инициализации
[см. исходник на github,
user-init-file и load в C-коде]
(на самом деле их больше, но другие варианты не кроссплатформенны).
Нам нужно определить какую именно исходную лисп-программу
инициализации мы возьмем за основу, в порядке приоритета: ~/_emacs
(устаревший — для MS-DOS), ~/.emacs
, ~/.emacs.el
,
~/.emacs.d/init.el
.
user-init-file while init | user-init-file after init | actual-user-init-file |
---|---|---|
nil emacs -q/--no-init-file |
nil |
prefer ~/.emacs.d/init.el |
~/.emacs relative by default |
~/.emacs relative by default |
~/.emacs |
~/_emacs |
~/_emacs |
~/_emacs |
~/_emacs.el |
~/_emacs.el |
~/_emacs.el |
~/_emacs.elc |
~/_emacs.el or ~/_emacs or ~/_emacs.elc |
~/_emacs.el or ~/_emacs |
~/.emacs |
~/.emacs |
~/.emacs |
~/.emacs.el |
~/.emacs.el |
~/.emacs.el |
~/.emacs.elc |
~/.emacs.el or ~/.emacs or ~/.emacs.elc |
~/.emacs.el or ~/.emacs |
~/.emacs.d/init.el |
~/.emacs.d/init.el |
~/.emacs.d/init.el |
~/.emacs.d/init.elc |
~/.emacs.d/init.el or ~/.emacs.d/init.elc |
~/.emacs.d/init.el |
(default "~/.emacs") (~/_emacs (file-truename "~/_emacs")) (~/_emacs.el (file-truename "~/_emacs.el")) (~/_emacs.elc (file-truename "~/_emacs.elc")) (~/.emacs (file-truename "~/.emacs")) (~/.emacs.el (file-truename "~/.emacs.el")) (~/.emacs.elc (file-truename "~/.emacs.elc")) (~/.emacs.d/init.el (file-truename "~/.emacs.d/init.el")) (~/.emacs.d/init.elc (file-truename "~/.emacs.d/init.elc"))
(defvar actual-user-init-file (let ( <<user-init-file-names>> ) (or (when (equal user-init-file nil) (or (cl-find-if #'file-exists-p (list ~/.emacs.d/init.el ~/_emacs ~/_emacs.el ~/.emacs ~/.emacs.el)) ~/.emacs.d/init.el)) (when (equal user-init-file default) ~/.emacs) (when (file-equal-p user-init-file ~/_emacs) ~/_emacs) (when (file-equal-p user-init-file ~/_emacs.el) ~/_emacs.el) (when (file-equal-p user-init-file ~/_emacs.elc) (or (when (file-exists-p ~/_emacs.el) ~/_emacs.el) ~/_emacs)) (when (file-equal-p user-init-file ~/.emacs) ~/.emacs) (when (file-equal-p user-init-file ~/.emacs.el) ~/.emacs.el) (when (file-equal-p user-init-file ~/.emacs.elc) (or (when (file-exists-p ~/.emacs.el) ~/.emacs.el) ~/.emacs)) (when (or (file-equal-p user-init-file ~/.emacs.d/init.el) (file-equal-p user-init-file ~/.emacs.d/init.elc)) ~/.emacs.d/init.el))))
Исходный код лисп-программы инициализации в файле actual-user-init-file вторичен по отношению к этому руководству, это не более чем автоматически сгенерированная из него программа. Но что если руководства не окажется в директории с настройками эмакса, и лисп-программа инициализации не сможет регенерировать себя? В таком случае программа инициализации должна попытаться скачать руководство из интернета.
(unless (file-exists-p well-tuned-emacs-reference-file) (condition-case error-signal (with-temp-file well-tuned-emacs-reference-file (url-insert-file-contents well-tuned-emacs-reference-url)) (error (cl-destructuring-bind (error-symbol . error-data) error-signal (message "Failed to download %s and save it as %s: %s - %s." well-tuned-emacs-reference-url well-tuned-emacs-reference-file error-symbol error-data)) (when (file-exists-p well-tuned-emacs-reference-file) (delete-file well-tuned-emacs-reference-file :move-to-trash)))))
Прежде мы должны условиться, что это руководство будет храниться в определенном месте, по умолчанию — в той же директории, что и актуальная лисп-программа инициализации эмакса; под определенным именем, по-умолчанию — README.org. Так же нам должен быть известен адрес свежей версии этого руководства в интернете. Встроенное в эмакс средство Customize позволит сделать настройки расположения файлов руководства полностью независимыми от прописанных в этом руководстве значений по-умолчанию. Потом эти настройки можно будет изменить в самом эмаксе и сохранить их значения на будущее, не меняя ни фрагменты кода в этом руководстве, ни код в сгенерированной лисп-программе инициализации. Для этого создадим в группе кастомизации Initialization подгруппу Well Tuned Emacs.
(defgroup well-tuned-emacs nil "Well Tuned Emacs initialization and customization settings." :link '(url-link "https://gitlab.com/zahardzhan/well-tuned-emacs") :version "25.0.50.1" :group 'initialization)
Добавим в эту группу две пользовательские настройки.
Emacs⊲ Environment⊲ Initialization⊲ Well Tuned Emacs⊲ Well Tuned Emacs Reference File ← файл README.org в директории с актуальной лисп-программой инициализации эмакса. Расположение файла этого руководства. Для обеспечения переносимости путей файлов между разными средами исполнения эмакс-лисп кода их следует указывать в формате POSIX, это позволит использовать один-и-тот-же файл одновременно с двух запущенных в разных средах экземпляров эмакса (например Windows/Cygwin/VMware).
actual-user-init-file | well-tuned-emacs-reference-file |
---|---|
~/.emacs or ~/.emacs.el or ~/_emacs or ~/_emacs.el |
~/README.org or ~/.emacs.d/README.org |
~/.emacs.d/init.el |
prefer ~/.emacs.d/README.org to ~/README.org |
(defcustom well-tuned-emacs-reference-file (let* ( <<user-init-file-names>> (~/readme (file-truename (concat (file-name-as-directory "~") "README.org"))) (~/.emacs.d/readme (file-truename (concat user-emacs-directory "README.org")))) (ignore default ~/_emacs.elc ~/.emacs.elc ~/.emacs.d/init.elc) (or (when (cl-find actual-user-init-file (list ~/.emacs ~/.emacs.el ~/_emacs ~/_emacs.el) :test #'equal) (or (when (file-exists-p ~/readme) ~/readme) ~/.emacs.d/readme)) (when (equal actual-user-init-file ~/.emacs.d/init.el) (or (when (file-exists-p ~/.emacs.d/readme) ~/.emacs.d/readme) (when (file-exists-p ~/readme) ~/readme) ~/.emacs.d/readme)))) "The Well Tuned Emacs Reference file." :type 'file :version "25.0.50.1" :group 'well-tuned-emacs)
Emacs⊲ Environment⊲ Initialization⊲ Well Tuned Emacs⊲ Well Tuned Emacs Reference URL ← https://gitlab.com/zahardzhan/well-tuned-emacs/raw/master/README.org. Адрес свежей версии этого руководства в интернете.
(defcustom well-tuned-emacs-reference-url "https://gitlab.com/zahardzhan/well-tuned-emacs/raw/master/README.org" "The Well Tuned Emacs Reference File on the internet." :type 'string :version "25.0.50.1" :group 'well-tuned-emacs)
Лисп-программа Customize [открыть её графический интерфейс в эмаксе — M-x customize], ставшая частью эмакса в середине девяностых — это краеугольный камень всей системы пользовательских настроек. Парадоксально, но подавляющее большинство фреймворков и личных настроек, доступных в сети, всеми силами избегают настройки эмакса с помощью встроенного в него интерфейса предназначенного именно для этой цели. Люди предпочитают настраивать эмакс написанием своего лисп-кода даже в тех случаях, когда этот лисп-код уже предусмотрительно написан, отлажен и задокументирован разработчиками лисп-программ, которые пользователь пытается настроить. Этот фатальный недостаток распространен повсеместно, но большинство пользователей эмакса считает такое положение дел нормальным.
Истина состоит в том, что GNU Emacs 25 имеет 3440 стандартных настройки в конфигурации по-умолчанию. Все они хорошо организованны, задокументированны и доступны для поиска и изменения в простом удобном и непривычном псевдографическом интерфейсе. Эти настроки сохраняются между сессиями эмакса, и многие из них выполнены в виде специфических лисп-программ. Подключение дополнительных модулей и пакетов расширений эмакса может запросто увеличить количество таких настроек до пяти тысяч. К чему приведет попытка изменения нескольких тысяч параметров управляемых лисп-кодом, меняющимся от версии-к-версии, написанием своего лисп-кода? Она практически неизбежно приведет к конфигурационному апокалипсису. Поэтому здесь и далее, и везде где только можно, я буду использовать систему Customize.
Emacs⊲ Help⊲ Customize⊲ Custom File ← ~/.emacs.d/custom.el. По-умолчанию Customize хранит свои данные в файле с исходным кодом лисп-программы инициализации эмакса; если мы переплетем этот файл — все наши настройки пропадут. В Customize можно выполнить настройку самой Customize, но фактически эта программа не может изменить место хранения своих данных, при том что такой параметр в ней есть — информация о том какой файл будет загружен хранится в самом этом файле, таким образом эта информация недоступна извне. Мы будем хранить настройки выполненные программой Customize в файле custom.el в директории ~/.emacs.d.
(let ((~/.emacs.d/custom.el (concat user-emacs-directory "custom.el"))) (setq custom-file ~/.emacs.d/custom.el) (when (file-exists-p custom-file) (load custom-file)) (unless (equal ~/.emacs.d/custom.el (car (get 'custom-file 'saved-value))) (add-hook 'after-init-hook (defun save-custom-file-location-in-custom-file () (customize-save-variable 'custom-file ~/.emacs.d/custom.el)))))
Ниже код вида (add-hook 'after-init-hook (defun … () … )) появится еще несколько раз, поэтому имеет смысл генерировать его с помощью макроса.
(defmacro customize-save-variable-after-init (var) `(add-hook 'after-init-hook (defun ,(make-symbol (concat "customize-save-variable-" (symbol-name var))) () (customize-save-variable ',var ,var))))
Многие режимы не активируются сами по себе при запуске эмакса, даже при том что в сохраненных настройках явно указано, что они должны быть активны. Поэтому после загрузки режимов приходится напоминать им об актививации.
(defun reinstate-mode (mode) (when (car (get mode 'saved-value)) (funcall mode 1)))
Чтобы не переплетать программу инициализации эмакса вручную после каждого редактирования этого руководства, сделаем так, что программа будет переплетать сама себя прямо во время запуска эмакса, если руководство было изменено после изменения программы.
(when (file-exists-p well-tuned-emacs-reference-file) (or (not (file-exists-p actual-user-init-file)) (file-newer-than-file-p well-tuned-emacs-reference-file actual-user-init-file)))
По всей видимости нет никакого тривиального способа заставить лисп-программу org-babel-tangle должным образом обрабатывать свойство заголовка блоков кода :tangle и связанный с ним аргумент target-file, указывающий в какой именно файл нужно сохранить сплетенную программу. Применим небольшой хак с перекрытием значения глобальной переменной user-init-file в динамической области видимости — таким образом мы укажем всем сторонним лисп-программам имя файла нашей новой программы инициализации на время сплетения и выполнения этой программы.
(progn (require 'ob-tangle) (message "Tangling %s → %s." well-tuned-emacs-reference-file actual-user-init-file) (let ((user-init-file actual-user-init-file)) (org-babel-with-temp-filebuffer well-tuned-emacs-reference-file (org-babel-tangle)) (load-file user-init-file)))
Как вариант, во время загрузки лисп-программы инициализации мы можем ее скомпилировать. Для этого нам понадобится лисп-программа байт-компиляции лисп-программ bytecomp. Следующая строка кода это своего рода шутка (нет) — она загружает лисп-программу байт-компиляции лисп-программ во время байт-компиляции нашей лисп-программы лисп-программой байт-компиляции лисп-программ.
(cl-eval-when (compile) (require 'bytecomp))
При интерпретации лисп-программы инициализации эмакса программа для сплетения этого руководства ob-tangle загружается непосредственно перед её использованием, и это не создает никаких проблем. Однако программа компиляции по возможности должна знать обо всех сторонних лисп-программах, которые могут быть загружены во время выполнения скомпилированной программы инициализации эмакса.
(cl-eval-when (compile) (require 'ob-tangle))
Определим переменную-условие компиляции программы инициализации эмакса well-tuned-emacs-compile-user-init-file как опцию в группе настроек этого руководства.
Emacs⊲ Environment⊲ Initialization⊲ Well Tuned Emacs⊲ Well Tuned Emacs Compile User Init File ← компилировать или не компилировать лисп-программу инициализации эмакса. При автоматической установке сохраненного значения этой опции системой Customize, а также при ручном включении/отключении этой опции в через интерфейс Customize, эмакс должен соответственно скомпилировать, или удалить скомпилированную программу инициализации. Для этого нам нужно написать функцию которая позаботится обо всем при изменении значения этой опции.
(defcustom well-tuned-emacs-compile-user-init-file nil "Compile or don't compile well-tuned Emacs user init file." :type 'boolean :set <<set-well-tuned-emacs-compile-user-init-file>> :version "25.0.50.1" :group 'well-tuned-emacs)
Загрузка эмакса становится довольно запутанной если добавить в нее возможность компиляции файла инициализации. С учетом описания того как происходит компиляция лисп-программ эмакса, запуск эмакса, загрузка лисп-программ эмакса, и того что происходит в нашей программе инициализации, мы должны учесть шесть возможных последовательностей выполнения лисп-программ при запуске эмакса:
- el→emacs [safe]
- el→tangle→el→emacs [safe]
- el→tangle→compile→elc→emacs [safe]
- elc→emacs [safe]
- elc→tangle→el↛emacs [unsafe (package-initialize)⇝user-init-file⇎load-file-name]
- elc→tangle→compile↛elc→emacs [unsafe (byte-compile elc)⇝cannot rename elc↦elc]
Компилировать или удалять программу инициализации прямо во время ее выполнения рискованно, поэтому шесть возможных вариантов развития событий в итоге сводятся к четырем.
compile | delete | |
---|---|---|
while init | compile after init | delete after init |
after init | compile el when there is no elc or elc older than el |
delete elc if there is el |
Таким образом, когда мы устанавливаем значение этой опции во время инициализации эмакса, выполнение соответствующих действий откладывается на потом.
(progn (defun well-tuned-emacs-compile-user-init-file () (let ((while-init-time (not after-init-time))) (cond (while-init-time (add-hook 'after-init-hook #'well-tuned-emacs-compile-user-init-file)) (after-init-time (require 'bytecomp) (if well-tuned-emacs-compile-user-init-file <<compile-el-when-there-is-no-elc-or-elc-older-than-el>> <<delete-elc-if-there-is-el>> ))))) (lambda (symbol value) (set symbol value) (well-tuned-emacs-compile-user-init-file)))
Компилируем лисп-программу инициализации только если скомпилированная программа старее, или её вовсе нет.
(when (file-exists-p actual-user-init-file)
(byte-recompile-file actual-user-init-file nil 0))
Просто удаляем скомпилированную программу инициализации, если у нас есть исходная программа инициализации.
(when (and (file-exists-p actual-user-init-file) (file-exists-p (byte-compile-dest-file actual-user-init-file))) (delete-file (byte-compile-dest-file actual-user-init-file)))
Осталось нанести последний штрих и общая программа инициализации эмакса будет готова. Система пакетов вошла в состав эмакса несколько лет назад, но все еще активно развивается и в некоторых местах требует ручного вмешательства. Если мы ею воспользуемся, система пакетов добавит код своей инициализации в сгенерированную программу инициализации эмакса. Чтобы этого избежать, достаточно добавить этот код самим, и сразу после этого мы можем выбрать и установить свои любимые пакеты.
<<initialize-package-system>> <<package-system-backports>> <<make-sure-melpa-is-used-as-one-of-community-package-archives>> <<make-sure-use-package-package-will-be-present>> (unless (cl-every #'package-installed-p package-selected-packages) (package-refresh-contents) (package-install-selected-packages)) (require 'use-package) <<setup-use-package>>
В обычных условиях для инициализации системы пакетов было бы достаточно одного единственного вызова функции (package-initialize), но особенности работы провайдера «Ростелеком» иногда приводят к повреждению кеша архивов. Поэтому при возникновении ошибки определенного типа кеш нужно очистить и затем попытаться провести повторную инициализацию системы пакетов.
(condition-case nil (package-initialize) (wrong-type-argument (cl-loop for (archive . _location) in package-archives with rel-path = "archives/%s/archive-contents" for archive-contents = (expand-file-name (format rel-path archive) package-user-dir) when (file-exists-p archive-contents) do (delete-file archive-contents)) (package-initialize)))
Конечно, перед автоматической установкой пакетов эмакс должен знать какие именно пакеты устанавливать и откуда их брать.
Emacs⊲ Applications⊲ Package⊲ Package Archives ← адреса архивов. По-умолчанию эмакс устанавливает пакеты из официального архива GNU ELPA. В этом архиве мало пакетов, но они надежные и доверенные. В неофициальных архивах MELPA[добавить] и Marmalade[добавить] пакетов гораздо больше, но они менее качественные в плане лицензионной чистоты и гарантий безопасности. Различия между этими архивами несущественны, но лично я предпочитаю MELPA, потому что он хостится на гитхабе.
(let ((melpa-location "http://melpa.milkbox.net/packages/")) (when (cl-loop for (_archive . location) in package-archives never (equal location melpa-location)) (cl-pushnew (cons "melpa" melpa-location) package-archives :test #'equal) (customize-save-variable-after-init package-archives)))
Emacs⊲ Applications⊲ Package⊲ Package Selected Packages ← имена вручную установленных пакетов. Каждый раз когда пользователь эмакса лично выбирает и устанавливает нужный ему пакет, эмакс сохраняет имя этого пакета в списке-значении переменной-опции package-selected-packages. Сама эта настройка появились только в GNU Emacs 25. В GNU Emacs 24 и более ранних версиях эмакса этой настройки нет; придется добавить ее самим.
(unless (boundp 'package-selected-packages) (defcustom package-selected-packages (list) "Store here packages installed explicitly by user." :type '(repeat symbol) :group 'package))
В новых версиях эмакса с опцией package-selected-packages связано гораздо больше функциональности, чем имело бы смысл портировать в старые версии эмакса. Но функция package-install-selected-packages того стоит — она автоматически устанавливает ваши любимые пакеты, по списку.
(unless (fboundp #'package-install-selected-packages) (defun package-install-selected-packages () "Ensure packages in `package-selected-packages' are installed." (interactive) (let ((packages-to-be-installed (cl-remove-if #'package-installed-p package-selected-packages))) (when packages-to-be-installed (when (or noninteractive (y-or-n-p (format "%s packages will be installed:\n%s, proceed?" (length packages-to-be-installed) (mapconcat #'symbol-name packages-to-be-installed ", ")))) (mapc #'package-install packages-to-be-installed))))))
Кроме ручной установки пакетов из графического интерфейса, нам понадобится средство для автоматической установки и грамотной загрузки установленных пакетов. В настоящее время для этой цели сообщество использует лисп-программу use-package.
(unless (cl-find 'use-package package-selected-packages) (cl-pushnew 'use-package package-selected-packages) (customize-save-variable-after-init package-selected-packages))
Use Package⊲ Use Package Always Ensure ← устанавливать пакеты лисп-программой use-package без необходимости использования ключа :ensure в коде вызова.
(unless use-package-always-ensure (setq use-package-always-ensure t) (customize-save-variable-after-init use-package-always-ensure))
На этом описание основной части программы инициализации завершено. Дальнейший текст рассказывает о важных стандартных настройках, нестандартных сочетаниях клавиш и конфигурации установленных пакетов.
Идейный преемник проекта Emacs Starter Kit — проект Better Defaults, выполнен Филом Хагельбергом [интервью] в виде пакета с небольшой лисп-программой. Эта лисп-программа, каждая строка которой тщательно отобрана сообществом, устанавливает значения пары десятков стандартных параметров в обход стандартной системы управления этими параметрами. Трудно найти более противоречивый проект. В некотором смысле это образцово-показательный забег по граблям. На мой взгляд, если современный Starter Kit стал гайдом, то логично было бы сделать гайдом и Better Defaults. Ниже я привожу ссылки на настройки некоторых ключевых параметров эмакса с пояснением причин по которым их стоит сделать. Списки сделанных настроек показывают лисп-программы M-x customize-saved и M-x customize-unsaved. Конечно, система кастомизации не всемогуща и для некоторых настроек (например, нестандартные сочетания клавиш) придется написать несколько строк на лиспе. В общем виде весь последующий код выглядит так:
<<definitions>> <<customizations>> <<keybindings>>
Начнем кастомизацию эмакса сверху и продолжим последовательно углубляться во всё более тонкие аспекты его работы. Но прежде — для удобства и быстроты настройки — лучше Org Confirm Elisp Link Function ← не подтверждать выполнение лисп-кода при переходе по ссылкам в этом руководстве и Org Return Follows Link ← переходить по ссылкам нажатием на Enter.
Frame Title Format ← имя буфера или полное имя файла/директории предваренное именем пользователя и машины при удаленном подключении. Как ни странно, заголовок фрейма (окна в оконном менеджере операционной системы) не кастомизируется стандартными средствами. Если открыто несколько фреймов, заголовок по-умолчанию совершенно бесполезен, поэтому используем наипростейший формат, позволяющий отличить один фрейм от другого.
(setq-default frame-title-format '(:eval (concat (when (file-remote-p default-directory) (let ((user (file-remote-p default-directory 'user)) (host (file-remote-p default-directory 'host))) (format "%s@%s:" user host))) (or buffer-file-truename dired-directory (buffer-name)))))
Menu Bar Mode ← главное меню спрятано. 80% опций в главном меню эмакса никогда не используются, остальные 20% продублированы в меню моделайна (mode-line — строка режимов под окном буфера). Меню буферов вызывается сочетаниями C-F10 и C-Left-Click в любом месте буфера, глобальное меню — по C-Right-Click, само главное меню — клавишей F10. Разумнее всего спрятать главное меню и показывать его при необходимости сочетанием C-x F10 (вариант C-M-F10 не подходит для Cygwin и Linux).
(global-set-key (kbd "C-x <f10>") #'toggle-menu-bar-mode-from-frame)
Tool Bar Mode ← панель инструментов отключена. Панель инструментов в эмаксе абсолютно бесполезна.
Scroll Bar Mode ← полоса прокрутки справа. Многие отключают полосу прокрутки по трем причинам: она не является частью стандартного интерфейса эмакса, она плохо реализована и эстетически убога. Но в то же время, нельзя отрицать ее очевидную пользу в графических средах даже в таком неполноценном виде.
Window Divider Mode ← широкая вертикальная разделительная черта между окнами отключена. Разделение окон по горизонтали широкой вертикальной чертой позволяет легко менять размеры окон мышкой при включенных полосах прокрутки. Выглядит старомодно, но в группе Window Divider есть настройки стиля.
Fringe face ← прозрачные поля. Во всех текстовых редакторах начиная с Блокнота принято иметь небольшие поля по краям области редактирования текста. Поля обязательно должны быть цвета фона чтобы не акцентировать внимание на артефактах рендеринга полосы прокрутки. Цветовые темы эмакса меняют цвета фона и полей, поэтому каждый раз при изменении темы нам нужно чтобы цвет полей соответствовал цвету фона. Для этого используем средство аспектно-ориентированного программирования Advice, которое изменяет поведение функций сторонних лисп-программ без изменения их оригинальной реализации. Функции-аспекты должны иметь как минимум такой же список аргументов, что и оригинальные функции, но байт-компилятор будет ругаться, если эти аргументы не будут использоваться, поэтому имена неиспользуемых аргументов должны начинаться с подчеркивания.
(let ((set-transparent-fringe (lambda (&rest _) (set-face-background 'fringe (face-attribute 'default :background))))) (advice-add #'load-theme :after set-transparent-fringe) (advice-add #'disable-theme :after set-transparent-fringe))
Indicate Empty Lines ← нет штриховки на полях. Штриховка на полях изящно выделяет пустую область за гранью буфера, но иногда отвлекает.
Uniquify Buffer Name Style ← за именами одинаковых буферов следует часть файлового пути.
Uniquify Separator ← имена одинаковых буферов отделены обратным слешем \ от файлового пути.
Size Indication Mode ← в моделайне отображается размер буфера.
Line Number Mode ← в моделайне отображается номер строки на которой находится курсор.
Column Number Mode ← в моделайне отображается номер столбца на котором находится курсор.
Initial Buffer Choice
← после запуска эмакс открывает файл с заметками
вместо стартового экрана, или как вариант —
после запуска эмакс открывает *scratch*
-буфер.
По желанию эмакс может открыть любой файл, директорию, сайт, программу
для чтения почты, новостей, чат, командную оболочку операционной
системы, музыкальный проигрыватель или новый закон природы.
Remember Notes Initial Major Mode ← эмакс открывает файл с заметками в режиме по-умолчанию, в режиме text-mode или в режиме org-mode. По умолчанию режим по-умолчанию — lisp-interaction-mode; поэтому предполагается, что это будут заметки с лисп-кодом для эмакса. В группе кастомизации Remember можно указать расположение файла с заметками и много других вещей.
Initial Scratch Message
← что угодно или ничего. Эмакс
всегда
открывает *scratch*
-буфер после запуска. От него невозможно
избавиться, но можно сделать
более полезным,
если добавить в него несколько ссылок на домашнюю директорию,
файл с личными паролями зашифрованный эмаксом
с помощью
GNU Privacy Guard, активные проекты, сайты и прочее.
(add-hook 'emacs-startup-hook (defun well-tuned-emacs-scratch-buffer-message () (with-current-buffer "*scratch*" (let ((scratch-buf-last-char (point-max))) (goto-char scratch-buf-last-char) (fancy-splash-insert :link (list "File" (lambda (_button) (call-interactively #'find-file)) "Specify a new file's name, to edit the file") " " :link (list "Home" (lambda (_button) (dired "~")) "Open home directory, to operate on its files") " " :link (list "Passwords" (lambda (_button) (let ((passwords "~/Dropbox/Passwords.org.gpg")) (when (file-exists-p passwords) (find-file passwords)))) "Open encrypted password vault")) (comment-region scratch-buf-last-char (point)) (newline) (goto-char (point-max)) (set-buffer-modified-p nil)))))
Cursor Type ← классический прямоугольный сплошной курсор или современная вертикальная черта между букв. Классический курсор предпочтительнее: он заметнее на больших экранах. Однако, при выделении текста такой курсор полностью перекрывает выделенную область под собой и из-за этого нельзя понять, выделен ли символ под курсором — до тех пор пока курсор не пропадет. Если курсор не мигает, эта задача и вовсе неразрешима. В качестве решения этой проблемы непрозрачный сплошной курсор можно сделать прозрачным после активации метки (inactive→active), а после деактивации метки (active→inactive) сделать его обратно сплошным:
i | → | a | a | → | i |
---|---|---|---|---|---|
■ | → | □ | □ | → | ■ |
■ | → | □ | ␣ | ␣ | |
␣ | ␣ | □ | □ | ||
␣ | ␣ | ␣ | ␣ |
Решение интересное, но не обязательное.
(let (inactive-mark-cursor-type) (add-hook 'activate-mark-hook (defun alter-cursor-type-after-activate-mark () (pcase (setq inactive-mark-cursor-type cursor-type) ('box (unless blink-cursor-mode (setq cursor-type 'hollow)))))) (add-hook 'deactivate-mark-hook (defun alter-cursor-type-after-deactivate-mark () (pcase inactive-mark-cursor-type ('box (pcase cursor-type ('hollow (setq cursor-type 'box))))))))
Blink Cursor Mode ← курсор мигает.
Make Pointer Invisible ← курсор мыши прячется при вводе текста.
Global Hl Line Mode ← текущая строка не подсвечивается или подсвечивается — когда это действительно необходимо.
Visible Bell ← в качестве предупреждения эмакс мерцает, а не звенит. Без этой настройки попытка сдвинуть курсор в пустом буфере вызовет раздражительный громкий звон.
Save Place Mode ← позиция курсора в буфере сохраняется между сессиями. После открытия файла редактирование продолжается с того места где было закончено.
Save Place File ← позиции курсоров сохраняются в файле ~/.emacs.d/places.
Require Final Newline ← в конец сохраняемого файла добавляется пустая строка.
Backup Directory Alist ← резервные копии файлов хранятся в директории ~/.emacs.d/backup. В противном случае резервные копии будут захламлять директории в которых находятся редактируемые файлы.
Global Auto Revert Mode ← буфер автоматически перезагружает содержимое файла при его изменении внешними программами.
Delete By Moving To Trash ← удаленные эмаксом файлы отправляются в корзину операционной системы.
Ido Mode & Ido Everywhere ← интерактивная навигация в минибуфере при работе с файлами и буферами.
Ido Enable Flex Matching ← более удобный поиск и выбор из множества вариантов во время интерактивной навигации в минибуфере.
Ido Save Directory List File ← состояние лисп-программы ido сохраняется в файле ~/.emacs.d/ido.
Ido Ubiquitous Mode ← интерактивная навигация в минибуфере при почти любом автодополнении. У этой лисп-программы есть некоторые проблемы (которые были исправлены прямо во время написания этого предложения с помощью пул реквеста на гитхабе и вечером того же дня все пользователи Emacs получили свои копии этого пакета уже без бага — суть философии разработки Emacs).
(use-package ido-ubiquitous :config (reinstate-mode 'ido-ubiquitous-mode))
Smex — интерактивная навигация в минибуфере при работе с M-x-командами эмакса.
(use-package smex :bind (("M-x" . smex) ("M-X" . smex-major-mode-commands) ("C-c C-c M-x" . execute-extended-command)))
Smex Save File ← состояние лисп-программы smex сохраняется в файле ~/.emacs.d/smex.
MULE Internationalization⊲ Default Input Method ← русский язык. Эмакс использует независимое от операционной системы переключение языков и методов ввода для обеспечения своей работы в очень разных средах. Переключение на русский язык по C-\ без предварительного указания метода ввода требует кастомизации. Кроме национальных методов ввода текста есть технические, например TeX (шутка в духе профессора), в этих режимах введенные спецслова превращаются в спецсимволы, например \'e → é, \th → þ, \Mu\epsilon\nu → Μεν, \existsa\forallb(b\ina) → ∃a∀b(b∈a).
C-\ не самое удобное сочетание клавиш, как вариант можно использовать свободное сочетание Shift-Space.
(global-set-key (kbd "S-SPC") #'toggle-input-method) (define-key isearch-mode-map (kbd "S-SPC") #'isearch-toggle-input-method)
Для эмакса написано много лисп-программ делающих работу со скобками более удобной и наглядной. Режимы Electric Pair Mode и Show Paren Mode — это встроенные лисп-программы, они достаточно хороши, но есть и получше, например Smartparens и Paredit. Однако все эти программы устарели, поэтому не стоит заморачиваться с их настройкой. Современные экспериментальные программы вроде Parinfer определяют структуру программы по отступам в коде и расставляют скобки автоматически.
(use-package smartparens-config :ensure smartparens :diminish smartparens-mode :config (progn <<bind-some-keys-for-smartparens-mode>> <<turn-on-smartparens-mode-for-some-modes>> (reinstate-mode 'smartparens-global-mode) (reinstate-mode 'show-smartparens-global-mode)))
Smartparens Global Mode ← скобки вводятся по-отдельности или парами во всех режимах. Необязательно включать этот режим везде, по-настоящему он полезен только при редактировании структурированного кода.
(add-hook 'prog-mode-hook #'turn-on-smartparens-strict-mode)
Show Smartparens Global Mode ← парные скобки подсвечиваются.
Smartparens включает в себя несколько альтернативных наборов сочетаний клавиш для манипуляции символьными выражениями (см. анимированное руководство). У каждого из этих наборов есть некоторые недостатки, поэтому я использую традиционный набор, свободный от этих недостатков насколько это позволяют незанятые клавиши эмакса и здравый смысл.
(bind-keys :map smartparens-mode-map <<classic-TI-Explorer-Zmacs-keys>> <<arrow-naviagion>> <<slurp/barf>> <<splice/unwrap>> ("C-k" . sp-kill-hybrid-sexp))
Текстовый редактор Zmacs в 80-х использовался как IDE для Common Lisp и ZetaLisp на лисп-машинах Texas Instruments Explorer и имел богатый репертуар команд для работы с символьными выражениями:
("C-M-f" . sp-forward-sexp) ("C-M-b" . sp-backward-sexp) ("C-M-n" . sp-next-sexp) ("C-M-p" . sp-previous-sexp) ("C-M-u" . sp-backward-up-sexp) ("C-M-d" . sp-down-sexp) ("C-M-(" . sp-beginning-of-sexp) ("C-M-)" . sp-end-of-sexp) ("C-M-t" . sp-transpose-sexp) ("C-M-<backspace>" . sp-backward-kill-sexp) ("C-M-k" . sp-kill-sexp) ("C-M-w" . sp-copy-sexp)
Консистентная навигация по S-выражениям стрелками.
("C-M-<up>" . sp-backward-up-sexp) ("C-M-<down>" . sp-down-sexp) ("C-M-<left>" . sp-backward-sexp) ("C-M-<right>" . sp-forward-sexp)
Команды сдвига скобок в соответствующих направлениях продублированы на случай если в терминале не работают стрелки.
("C-<left>" . sp-backward-slurp-sexp) ("C-<right>" . sp-forward-slurp-sexp) ("M-<left>" . sp-forward-barf-sexp) ("M-<right>" . sp-backward-barf-sexp) ("C-," . sp-backward-slurp-sexp) ("C-." . sp-forward-slurp-sexp) ("C-M-," . sp-forward-barf-sexp) ("C-M-." . sp-backward-barf-sexp)
Сплайсы и удаления скобок поднимают S-выражение на уровень выше в дереве S-выражений, или делают его структуру более плоской.
("C-<up>" . sp-splice-sexp-killing-around) ("C-<down>" . sp-splice-sexp) ("M-[" . sp-backward-unwrap-sexp) ("M-]" . sp-unwrap-sexp)
Пусть удаление слова назад (см. C-w — backward-kill-word-or-kill-region) сохраняет структуру S-выражений.
(advice-add #'backward-kill-word-or-kill-region :around (defun backward-kill-word-preserve-s-expression-structure (fn &rest args) (if (when smartparens-mode (not (region-active-p))) (sp-backward-kill-word (cl-first args)) (apply fn args))))
Indent Tabs Mode ← отступы пробелами.
Tab Always Indent ← после автоматической установки отступов Tab выполняет автодополнение.
Subword Mode ← составные части слова записанного CamelCase'ом считаются отдельными словами. Подробнее у Xah Lee.
Superword Mode ← составные слова набранные в любом стиле считаются одним словом. Режимы Subword и Superword взаимно исключают друг друга. Одновременно может быть активен только один из режимов.
Delete Selection Mode ← выделенный текст полностью удаляется или заменяется при удалении, вставке или вводе. Стандартное поведение современных текстовых редакторов.
Mouse Yank At Point ← мышь вставляет текст на позиции текстового курсора, а не на позиции курсора мыши. В традиции X Window System текст вставляется по щелчку средней кнопки мыши.
X Window System и Emacs (в любой ОС) поддерживают два буфера обмена: primary и clipboard. Мышь работает с primary-буфером, клавиатурные команды — с clipboard-буфером.
X Select Enable Clipboard ← клавиатурные команды копирования и вставки используют системный буфер обмена (по-умолчанию).
X Select Enable Primary ← клавиатурные команды копирования и вставки используют мышиный буфер обмена (в дополнение к системному).
Save Interprogram Paste Before Kill ← фрагменты текста скопированные в буфер обмена во внешних программах сохраняются в эмаксе в буфере скопированных и удаленных фрагментов текста kill ring.
Yes or No Predicate ← подтверждение одной клавишей: Y или Пробел — да, N или Delete — нет.
(defalias (function yes-or-no-p) (function y-or-n-p))
Apropos Do All ← расширенный поиск команд, функций, переменных и документации командами apropos.
О настройке режима Org можно написать целую книгу, но есть несколько простых опций, которые полезны для работы с этим руководством.
Org Support Shift Select ← выделение шифтом и стрелками в режиме Org.
Org Src Fontify Natively ← код в блоках подсвечивается как обычный текст.
Org Src Window Setup ← по C-c ' редактор кода открывается в окне документа или в отдельном окне (по-умолчанию).
Org Confirm Babel Evaluate ← код в блоках выполняется без предварительного подтверждения.
Настройка шрифтов в эмаксе очень специфична и системно-зависима. С учетом всего разнообразия операционных систем и окружений в которых может работать эмакс, в нем невозможно настроить шрифты стандартными средствами так чтобы они работали везде должным образом. Следующие настройки позволят эмаксу использовать шрифт лучше всего подходящий его окружению.
Well Tuned Emacs Fonts ← списки предпочитаемых шрифтов в разных системных окружениях. Это ассоциативный список в котором множеству системных окружений соответствует множество шрифтов в порядке предпочтения.
(defcustom well-tuned-emacs-fonts (quote (((gnu/linux gnu/kfreebsd darwin windows-nt cygwin) "Consolas-10" "Courier New-9" "Monaco-9" "Anonymous Pro-11" "Cambria-11" "Times New Roman-11" "Georgia-10" "DejaVu Serif Condensed-10" "Arial-10" "Droid Sans-10" "Segoe UI Symbol-10" "Lucida Sans Unicode-10" "Corbel-11" "Droid Sans Mono-10" "Envy Code R-10" "Menlo-10") ((gnu/linux gnu/kfreebsd) "Inconsolata-10" "DejaVu Sans Mono-10" "Ubuntu Mono-10"))) "Preferred fonts for operating system environments." :type '(alist :key-type (set :tag "Type of operating system" (const :tag "A GNU/Linux system—that is, a variant GNU system, using the Linux kernel." gnu/linux) (const :tag "A GNU (glibc-based) system with a FreeBSD kernel." gnu/kfreebsd) (const :tag "The GNU system (using the GNU kernel, which consists of the HURD and Mach)." gnu) (const :tag "Darwin (Mac OS X)." darwin) (const :tag "Microsoft Windows NT, 9X and later." windows-nt) (const :tag "Cygwin, a Posix layer on top of MS-Windows." cygwin) (const :tag "Microsoft’s DOS." ms-dos)) :value-type (repeat :tag "Preferred fonts" (string :tag "Font"))) :version "25.0.50.1" :group 'well-tuned-emacs)
Соответствующая функция возвращает список предпочитаемых шрифтов в текущем системном окружении.
(defun well-tuned-emacs-fonts () (cl-loop for (systems . fonts) in well-tuned-emacs-fonts when (member system-type systems) append fonts into system-fonts finally return (cl-remove-duplicates system-fonts :test #'equal :from-end t)))
available-font → полное имя шрифта, если шрифт с указанным кратким именем доступен в текущем системном окружении. Осторожно: применение функции find-font в консольном Emacs 24.5.1 под Debian Stretch 64 приведет к ошибке сегментации и краху программы.
(defun available-font (font) (when (stringp font) (when window-system (find-font (font-spec :name font)))))
available-well-tuned-emacs-fonts → список предпочитаемых шрифтов доступных в текущем системном окружении.
(defun available-well-tuned-emacs-fonts () (cl-remove-if-not #'available-font (well-tuned-emacs-fonts)))
Well Tuned Emacs Font ← выбранный пользователем шрифт для каждого конкретного системного окружения. По-умолчанию выбирается наиболее предпочтительный шрифт из доступных в текущем системном окружении.
(defcustom well-tuned-emacs-font (list (cons system-type (cl-first (available-well-tuned-emacs-fonts)))) "Chosen fonts for operating systems." :type '(alist :key-type (choice :tag "Type of operating system" (const :tag "A GNU/Linux system—that is, a variant GNU system, using the Linux kernel." gnu/linux) (const :tag "A GNU (glibc-based) system with a FreeBSD kernel." gnu/kfreebsd) (const :tag "The GNU system (using the GNU kernel, which consists of the HURD and Mach)." gnu) (const :tag "Darwin (Mac OS X)." darwin) (const :tag "Microsoft Windows NT, 9X and later." windows-nt) (const :tag "Cygwin, a Posix layer on top of MS-Windows." cygwin) (const :tag "Microsoft’s DOS." ms-dos)) :value-type (string :tag "Font")) :set (progn <<well-tuned-emacs-font>> (lambda (symbol value) (when (listp value) (set symbol value) (well-tuned-emacs-font (well-tuned-emacs-font))))) :version "25.0.50.1" :group 'well-tuned-emacs)
Соответствующая функция устанавливает или возвращает выбранный пользователем шрифт для текущего системного окружения.
(defun well-tuned-emacs-font (&optional font) (let ((current-font (cdr (assoc system-type well-tuned-emacs-font)))) (or (unless font current-font) (when (stringp font) (unless (equal font current-font) (setq well-tuned-emacs-font (cl-subst (cons system-type font) (cons system-type current-font) well-tuned-emacs-font :test #'equal))) (when (available-font font) (set-frame-font font :keep-size t))))))
Быстро выбрать шрифт можно двумя способами: автодополнением по имени шрифта через сочетание клавиш C-x M-f [«Meta-Font»]
(global-set-key (kbd "C-x M-f") (defun select-font () (interactive) (well-tuned-emacs-font (completing-read "Select font: " (available-well-tuned-emacs-fonts)))))
или последовательно перебирая доступные шрифты сочетаниями клавиш C-x C-[</>] и далее С-[</>] или [</>] до тех пор, пока не будет найден нужный шрифт (аналогично выбору размера шрифта программой text-scale-adjust по C-x C-[+/−]).
(defun adjust-font (inc) (interactive "p") (well-tuned-emacs-font (or (cycle-around (well-tuned-emacs-font) (pcase (event-basic-type last-command-event) ((or ?< ?,) (- inc)) ((or ?> ?.) inc) (_ inc)) (available-well-tuned-emacs-fonts)) (cl-first (available-well-tuned-emacs-fonts)))) (message (format "%s. Use < and > for further adjustment." (well-tuned-emacs-font))) (set-transient-map (let ((map (make-sparse-keymap))) (dolist (mod '(() (control))) (dolist (key '(?< ?> ?, ?.)) (define-key map (vector (append mod (list key))) (lambda () (interactive) (adjust-font (abs inc)))))) map)))
(cl-loop for key in '(?< ?> ?, ?.) do
(define-key ctl-x-map (vector (list 'control key)) #'adjust-font))
Цикл по последовательности вокруг элемента заключается в выборе другого элемента последовательности отстоящего от указанного на некоторое количество позиций. Указанный элемент может быть результатом выбора только если последовательность не содержит никаких других элементов.
(cl-defun cycle-around (item times seq &key (test #'equal)) (if (zerop times) item (let ((times (if (cl-plusp times) (1- times) times))) (cl-loop for (i . tail) on seq collect i into head when (funcall test i item) return (let ((cycle (cl-remove item (append tail head) :test test))) (or (when cycle (nth (mod times (length cycle)) cycle)) (unless cycle item)))))))
Дополнительно можно добавить выбор из всех доступных семейств шрифтов по C-x C-M-f
(global-set-key (kbd "C-x C-M-f") (defun select-font-family () (interactive) (well-tuned-emacs-font (completing-read "Select font: " (font-families)))))
и переключение между ними по C-x M-[</>] и C-x C-M-[</>].
(defun adjust-font-family (inc) (interactive "p") (well-tuned-emacs-font (or (cycle-around (font-family (well-tuned-emacs-font)) (pcase (event-basic-type last-command-event) ((or ?< ?,) (- inc)) ((or ?> ?.) inc) (_ inc)) (font-families)) (cl-first (font-families)))) (message (format "%s. Use < and > for further adjustment." (well-tuned-emacs-font))) (set-transient-map (let ((map (make-sparse-keymap))) (dolist (mod '(() (meta) (control meta))) (dolist (key '(?< ?> ?, ?.)) (define-key map (vector (append mod (list key))) (lambda () (interactive) (adjust-font-family (abs inc)))))) map)))
(cl-loop for key in '(?< ?> ?, ?.) do (cl-loop for mod in '((meta) (control meta)) do (define-key ctl-x-map (vector (append mod (list key))) #'adjust-font-family)))
font-families → упорядоченный список всех доступных семейств шрифтов.
(defun font-families () (funcall (compose (lambda (font-families) (sort font-families (lambda (x y) (string< (upcase x) (upcase y))))) (lambda (font-families) (cl-remove-duplicates font-families :test #'string=)) (lambda (font-families) (cl-remove-if-not #'available-font font-families))) (font-family-list)))
font-family → семейство к которому принадлежит указанный шрифт.
(defun font-family (font-name) (when font-name (funcall (compose (function symbol-name) (lambda (font-spec) (font-get font-spec :family)) (lambda (font-name) (font-spec :name font-name))) font-name)))
compose → композиция функций:
(compose e f … g h) = e ∘ f ∘ … ∘ g ∘ h = λx.e(f(…(g(hx)))
основа кода этой функции взята из книги “ANSI Common Lisp” (1995), автор — Paul Graham (см. о книге на сайте автора); страница 110. Можно считать это просто рекомендацией хорошей книги основателя news.ycombinator.com. Занятно: 20 лет назад, всего через 8 месяцев после выхода Windows 95 вышла книга в которой автор рассказывает о практическом применении выразительных средств которые впоследствии станут мейнстримом только через 15-20 лет. Еще более занятно то, что все эти средства уже тогда можно было использовать в древнем даже по тем временам текстовом редакторе двадцатилетней давности.
(defun compose (&rest functions) (cl-destructuring-bind (first . rest) (reverse functions) (lambda (&rest args) (cl-reduce (apply-partially #'flip #'funcall) rest :initial-value (apply first args)))))
Фрагмент (lambda (v f) (funcall f v)) в оригинальном коде Пола Грэма показался мне недостаточно изящным и быстрый поиск в интернете указал на маленькую полезную абстракцию:
flip → результат применения функции при перемене мест аргументов. Эта функция определена в стандарте Haskell 2010: Chapter 9. Standart Prelude:
– flip f takes its (first) two arguments in the reverse order of f.
flip :: (a → b → c) → b → a → c
flip f x y = f y x
(defun flip (f x y) (funcall f y x))
Сочетания клавиш в эмаксе имеют три ярко выраженные особенности: их очень много; их трудно запомнить; и они вызывают повреждения рук при злоупотреблении. С запоминанием помогут быстрые подсказки в формате PDF: «Справочник команд GNU Emacs», «Org-Mode Reference Card», «Dired Reference Card»; и лисп-программа Guide Key.
(use-package guide-key :diminish guide-key-mode :config (reinstate-mode 'guide-key-mode))
Guide Key Mode & Guide Key Sequence ← автоматически показывать доступные сочетания клавиш для префиксов C-x [добавить], C-c [добавить], C-h [добавить], M-s [добавить], M-g [добавить], F** [добавить], ESC [добавить].
Guide Key Popup Window Position ← окно с подсказками сочетаний клавиш появляется снизу или справа (по-умолчанию).
Для снижения нагрузки на левую руку при вводе команд эмакса многие люди советуют поменять местами клавиши Caps Lock и Control. Раньше я тоже так делал, но этого явно недостаточно. На современных стандартных клавиатурах кнопку Caps Lock нажимать удобнее, чем Control, но это не избавляет от нагрузки на левую руку, а всего лишь незначительно снижает ее. Лучше полностью отказаться от клавиш Control и Caps Lock и использовать в качестве модификатора «C-» зажатую клавишу «пробел». Решение не идеальное, но для здоровья рук оно полезнее, чем Caps ⇆ Ctrl.
Сочетание клавиш C-w — де-факто стандарт для удаления слова слева от курсора.
(defun backward-kill-word-or-kill-region (arg) (interactive "p") (if (region-active-p) (kill-region (region-beginning) (region-end)) (backward-kill-word arg)))
(global-set-key (kbd "C-w") #'backward-kill-word-or-kill-region) (define-key isearch-mode-map (kbd "C-w") #'backward-kill-word-or-kill-region) (define-key minibuffer-local-map (kbd "C-w") #'backward-kill-word-or-kill-region) (add-hook 'ido-setup-hook (defun well-tuned-emacs-bind-ido-mode-delete-backward-word-updir () (when (boundp 'ido-completion-map) (when (fboundp #'ido-delete-backward-word-updir) (define-key ido-completion-map (kbd "C-w") #'ido-delete-backward-word-updir)))))
Поиск по шаблонам регулярных выражений более актуален в качестве поиска по-умолчанию.
(global-set-key (kbd "C-s") #'isearch-forward-regexp) (global-set-key (kbd "C-r") #'isearch-backward-regexp) (global-set-key (kbd "C-M-s") #'isearch-forward) (global-set-key (kbd "C-M-r") #'isearch-backward)
Быстрое переключение между режимами Org и Text по M-F1 и M-F2 для удобного редактирования этого руководства.
(with-eval-after-load "text-mode" (when (boundp 'text-mode-map) (define-key text-mode-map (kbd "M-<f2>") #'org-mode))) (with-eval-after-load 'org (when (boundp 'org-mode-map) (define-key org-mode-map (kbd "M-<f1>") #'text-mode)))
Логичнее удалять текст до буквы, чем до-с буквой.
(autoload #'zap-up-to-char "misc" "Kill up to, but not including ARGth occurrence of CHAR." t) (global-set-key (kbd "M-z") #'zap-up-to-char)
Лисп-программа ibuffer это улучшенная версия программы управления буферами list-buffers.
(global-set-key (kbd "C-x C-b") #'ibuffer)
Для быстрого убийства буфера хорошо подходит сочетание C-x C-k. По-умолчанию на него назначен очень редко используемый набор сочетаний клавиш для отладки макросов, который логично смотрится на месте свободного сочетания C-x M-k [«keyboard macro»-команды].
(global-set-key (kbd "C-x C-k") #'kill-this-buffer) (global-set-key (kbd "C-x M-k") #'kmacro-keymap)
Автодополнение текста выполняется программой hippie-expand, которая включает в себя стандартную программу автодополнения dabbrev-expand и несколько других программ, выдающих дополнения определенного вида в желаемом порядке.
(global-set-key (kbd "M-/") #'hippie-expand)
Hippie Expand Try Functions List ← мощная, но умеренная последовательность вариантов автодополнений Саши или просто умеренная последовательность автодополнений Божидара. Оригинальная последовательность автодополнений иногда выдает варианты с небывалым размахом.
Команда unfill-paragraph, назначенная на свободное сочетание C-x M-q, делает ровно противоположное команде fill-paragraph (M-q) — она превращает абзац разбитый на несколько коротких строк в одну большую строку.
(global-set-key (kbd "C-x M-q") (defun unfill-paragraph (&optional region) "Takes a multi-line paragraph and makes it into a single line of text." (interactive (progn (barf-if-buffer-read-only) (list t))) (let ((fill-column (point-max))) (fill-paragraph nil region))))
В Windows 7/8/10 эмакс по-умолчанию считает своей домашней директорией значение переменной окружения
{~} → (getenv "AppData") → C:\Users\User\AppData\Roaming
в то время как в UNIX-совместимых ОС подразумевается директория
{~} → (getenv "UserProfile") → C:\Users\User
Эмакс будет считать своей домашней директорией путь прописанный в переменной окружения Home, если она определена. Её значение можно изменить с помощью команды Windows setx.
(when (eq system-type 'windows-nt) (unless (getenv "Home") (shell-command (format "setx \"%s\" \"%s\"" 'Home (getenv "UserProfile")))))
Установка домашней рабочей директории (cd "~") — в свойствах ярлыка «Рабочая папка».
Исправления некоторых багов GNU Emacs:
Emacs 25 testing: org-html-export returns org-html-fontify-code: Wrong number of arguments…
(when (= emacs-major-version 25) (declare-function font-lock-fontify-buffer "font-lock" ()) (defun org-font-lock-ensure () (font-lock-fontify-buffer)))
Роман Захаров zahardzhan@gmail.com.
;; Copyright © Roman Zaharov <zahardzhan@gmail.com> ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;;; Code:
(provide 'well-tuned-emacs)