Как загружают свои настройки нормальные текстовые редакторы и IDE? При запуске они ищут некий каталог, а внутри него — конфигурационные файлы. Если такие файлы есть, они их загружают и используют.

Неужели Emacs настолько сильно отличается от них? Да, отличается.


Во-первых, Emacs ищет свои конфигурационные файлы в нескольких местах.

  1. ~/.emacs.el — совсем старый способ хранения конфигурации, не используйте. Держать под контролем VCS весь домашний каталог плохая идея.

  2. ~/.emacs.d/init.el — более современный способ. Держать один небольшой каталог под контролем VCS очень просто.

  3. ~/.config/emacs/init.el — самый современный способ хранения настроек, соответствующий спецификации XDG.

Мы сейчас не будем рассматривать файлы early-init.el и конфигурационные файлы на уровне ОС, чтобы не усложнять.

Во-вторых, Emacs ищет свой конфигурационный файл в указанном порядке, и как только находит, сразу останавливает дальнейшие поиски. То есть, если файл ~/.emacs.el существует, Emacs не станет загружать ничего из файлов ~/.emacs.d/init.el или ~/.config/emacs/init.el, даже если они существуют.

Чтобы ваш init.el не зависел от каталога, вы можете использовать специальную переменную user-emacs-directory, значение которой вычисляется автоматически. Например, в этом случае настройка пакета tempel может выглядеть следующим образом:

;; 📦 TEMPEL
;; https://elpa.gnu.org/packages/doc/tempel.html
;; Сниппеты
(use-package tempel
  :pin gnu
  :ensure t
  :bind
  (:map global-map
    ("M-+" . tempel-complete)
    ("M-*" . tempel-insert))
  :custom
  (tempel-path `(,(expand-file-name "templates/asciidoc-ts.eld" user-emacs-directory)
                 ,(expand-file-name "templates/emacs-lisp.eld" user-emacs-directory)
                 ,(expand-file-name "templates/markdown.eld" user-emacs-directory)
                 ,(expand-file-name "templates/rst.eld" user-emacs-directory))))

В каталоге с init.el (не важно в каком именно) у меня находится подкаталог templates/, а в нём — файлы шаблонов под разные режимы.

В третьих, разберём особенности загрузки и выполнения функций при инициализации. В Emacs можно сделать конфиг на 2000 строк, который будет загружаться за полсекунды, но всё зависит от вас.

require

Функция require загружает код и немедленно его выполняет. Это устаревший способ загрузки пакетов, хотя в некоторых случаях его использование всё ещё оправдано:

(require 'custom)
(setopt custom-file
        (expand-file-name
         (convert-standard-filename "custom.el")
         user-emacs-directory))

:init

Многие люди, пользующиеся use-package, часто добавляют код настройки в блок :init, например, так:

(use-package whitespace
  :init (whitespace-mode 1))

Удивительно, что этот код вообще работает. Потому что технически он раскрывается в вот такую последовательность команд:

;; Включаем режим
(whitespace-mode 1)
;; Теперь загрузим сам модуль
(require 'whitespace)

Я уже писал в одной из своих статей, что :init должен содержать код, который выполняется до загрузки пакета!

:config

Использование :config вполне оправдано.

(use-package whitespace
  :config (whitespace-mode 1))

Это код, который выполняется после загрузки пакета, то есть раскрывается вот в такую последовательность:

(require 'whitespace)
(whitespace-mode 1)

Это работает именно так, как и ожидается. При этом режим whitespace-mode включается сразу же после загрузки пакета. Это может немного замедлить запуск Emacs.

:hook

А теперь посмотрим, как можно оптимизировать запуск Emacs с помощью всего одного хука.

(use-package whitespace
  :hook (after-init . whitespace-mode))

Технически этот код раскрывается в такую последовательность команд:

(require 'whitespace)
(add-hook 'after-init-hook #'whitespace-mode)

Код пакета загружен, но режим не включается мгновенно. Вместо этого мы откладываем момент его включения на период "после инициализации Emacs".

Примеры

Хотите ещё примеров? Пожалуйста! (Честно говоря, вся статья ради них и написана).

  1. DELSEL

    ;; 📦 DELSEL
    ;; Встроенный пакет.
    ;; Используется для управления удалением выделенного текста.
    (use-package delsel
      :hook
      (after-init . delete-selection-mode)) ;; Удалять выделенный фрагмент при вводе текста
  2. DESKTOP

    ;; 📦 DESKTOP
    ;; Сохранение состояния Emacs между сессиями.
    ;; https://www.gnu.org/software/emacs/manual/html_node/emacs/Saving-Emacs-Sessions.html
    (use-package desktop
      :custom
      (desktop-dirname user-emacs-directory "Каталог для хранения файла .desktop.")
      (desktop-load-locked-desktop t "Загрузка файла .desktop даже если он заблокирован.")
      (desktop-restore-frames t "Восстанавливать фреймы.")
      (desktop-save t "Сохранять список открытых буферов, файлов и т. д. без лишних вопросов.")
      :config
      ;; Буферы с этими режимами мы сохранять не будем
      (add-to-list 'desktop-modes-not-to-save 'dired-mode)
      (add-to-list 'desktop-modes-not-to-save 'image-mode)
      :hook
      (after-init . desktop-save-mode))
    
  3. HL-LINE

    ;; 📦 HL-LINE-MODE
    ;; Подсветка активной строки.
    (use-package hl-line
      :hook
      (after-init . global-hl-line-mode))
  4. PAREN

    ;; 📦 PAREN
    ;; Подсветка парных скобок.
    (use-package paren
      :hook
      (after-init . show-paren-mode))
  5. PIXEL-SCROLL, режим для HiDPI-мониторов:

    ;; 📦 PIXEL-SCROLL
    ;; Встроенный пакет, позволяет плавно прокручивать текст
    (when (package-installed-p 'pixel-scroll)
      (use-package pixel-scroll
        :hook
        (after-init . pixel-scroll-mode)
        (after-init . pixel-scroll-precision-mode)))
  6. REPEAT-MODE, упрощающий ввод некоторых команд:

    ;; 📦 REPEAT-MODE
    ;; Встроенный пакет для повторения типовых действий
    (use-package repeat
      :hook
      (after-init . repeat-mode))
  7. SAVEPLACE, запоминает позицию курсора в файле:

    ;; 📦 SAVEPLACE
    ;; Запоминание позиции курсора в посещённых файлах.
    (use-package saveplace
      :custom
      (save-place-forget-unreadable-files t "Не запоминать положение в нечитаемых файлах.")
      :hook
      (after-init . save-place-mode))
  8. SAVEHIST, запоминание списка последних файлов и каталогов:

    ;; 📦 SAVEHIST
    ;; Встроенный пакет для запоминания истории команд
    (use-package savehist
      :defer nil
      :custom
      (savehist-additional-variables
        '(compile-history
           regexp-search-ring
           search-ring
           shell-command-history))
      :config
      (add-to-list 'delete-frame-functions 'savehist-save)
      :hook
      (kill-emacs . savehist-save)
      (server-done . savehist-save)
      (after-init . savehist-mode))
  9. CoRFU, Completion in Region Functions, автодополнение для обычных буферов:

    ;; 📦 CORFU
    ;; https://elpa.gnu.org/packages/corfu.html
    ;; Расширение для автодополнения в буфере.
    (use-package corfu
      :pin gnu
      :ensure t
      :custom
      (corfu-auto-prefix 2 "По умолчанию — 3, это много.")
      (corfu-auto-delay 0.3 "Немного увеличим задержку, чтобы не тормозило.")
      :hook
      (after-init . global-corfu-mode) ;; Включим глобально
      (corfu-mode . corfu-indexed-mode)) ;; Номера возле вариантов завершения
  10. EDITORCONFIG, поддержка EditorConfig:

    ;; 📦 EDITORCONFIG
    ;; Поддержка https://editorconfig.org/
    ;; https://github.com/editorconfig/editorconfig-emacs
    (use-package editorconfig
      :pin gnu
      :ensure t
      :init
      (unless (alist-get 'editorconfig package-alist)
        (package-upgrade 'editorconfig))
      :mode
      ("\\.editorconfig\\'" . editorconfig-conf-mode)
      :hook
      (after-init . editorconfig-mode))