Редакторы и IDE Emacs: философия, org-mode, elisp и почему он всё ещё жив
0%

Emacs: философия, org-mode, elisp и почему он всё ещё жив

Emacs: философия, org-mode, elisp и почему он всё ещё жив

Есть программа, которой в 2026 году исполняется пятьдесят лет, и она по-прежнему получает мажорные релизы, по-прежнему собирает несколько процентов профессиональных разработчиков и по-прежнему вызывает у них странную привязанность, которую сложно объяснить человеку со стороны. Речь про Emacs.

Объяснение, которое обычно дают («там классные горячие клавиши», «там org-mode»), неверно. Горячие клавиши у Emacs, честно говоря, спорные. Org-mode впечатляет, но это следствие, а не причина. Настоящая причина одна, и она архитектурная: Emacs — это не редактор с плагинами, а работающая Lisp-машина, у которой редактор написан на Lisp и который вы можете переписать, не выходя из него.

Дальше — что из этого следует, сколько это стоит и в каких случаях оно того не стоит.

Откуда всё взялось

Emacs родился в 1976 году в MIT AI Lab как набор макросов для редактора TECO — отсюда и имя, Editing MACroS. Ричард Столлман и Гай Стил собрали воедино чужие наработки, и получившаяся штука сразу оказалась не редактором, а средой, где редактор конфигурируется кодом. В 1985 году Столлман написал GNU Emacs с нуля — уже с собственным диалектом Lisp, — и это стало первой большой программой проекта GNU. Каноническое изложение замысла — его статья EMACS: The Extensible, Customizable Display Editor (1981). Три слова из заголовка — extensible, customizable, self-documenting — до сих пор работают как техническое задание.

Из этой таблицы важен не список версий, а форма кривой. Emacs десять лет выглядел законсервированным, а с 27-й версии втянул внутрь себя ровно то, что делало современные редакторы современными: быстрый JSON (без него LSP тормозил), tree-sitter, LSP-клиент, нативную компиляцию. Проект не «дожил» — он догнал.

Архитектура: 5% на C, всё остальное на Lisp

Ключ ко всему — пропорция.

Слои Emacs

На C написан минимум: работа с текстовым буфером (классический gap buffer — массив с «дыркой» в позиции курсора, дешёвая вставка там, где вы печатаете, дорогая — при прыжках по файлу), перерисовка экрана, сборщик мусора, ввод-вывод, процессы и примитивы языка. Всё остальное — открытие файла, поиск, дired, почтовый клиент, git-интерфейс, режим для Python — это Emacs Lisp, который загружается при старте и который можно переопределить сейчас, прямо в запущенном процессе.

Разница с моделью «плагин через API» принципиальная. В VS Code расширение обращается к vscode.* — к тому, что команда редактора решила вынести наружу; остальное закрыто и живёт в другом процессе. В Emacs нет «наружу»: функция find-file, которой вы открываете файлы, — обычная лисповая функция, её исходник открывается по C-h f find-file двумя нажатиями, и её поведение можно изменить, не форкая проект.

;; Штатное поведение: C-x C-f открывает файл.
;; Хотим, чтобы при открытии файла в /etc он молча открывался под sudo.
(defun my/etc-as-root (orig-fn filename &rest args)
  "Обёртка вокруг find-file: подменяем путь на TRAMP-путь с sudo."
  (if (and (string-prefix-p "/etc/" filename)
           (not (file-writable-p filename)))
      (apply orig-fn (concat "/sudo::" filename) args)
    (apply orig-fn filename args)))

(advice-add 'find-file :around #'my/etc-as-root)
;; Отменить: (advice-remove 'find-file #'my/etc-as-root)

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

Цена у этого честная и заметная: раз всё редактируется, то всё и ломается. Кривая advice в чужом пакете кладёт редактор так же надёжно, как кривой патч в ядре.

Всё есть буфер

Второй столп философии — единая абстракция. В Emacs любое содержимое — это буфер: открытый файл, вывод команды, интерфейс git, почтовый ящик, список директории, окно дополнения. Буфер существует в памяти независимо от того, показан ли он на экране.

Фрейм, окна и буферы

Терминология Emacs старше принятой и потому путает:

Термин Emacs Что это на самом деле Аналог в других редакторах
buffer содержимое в памяти буфер / открытая вкладка (данные)
window прямоугольник, показывающий буфер сплит / панель
frame окно операционной системы окно приложения
point позиция курсора caret
mark вторая точка, задаёт регион якорь выделения
minibuffer строка внизу для ввода команд command palette

Практический смысл единой абстракции: все навыки переносятся. Умеете искать по буферу — умеете искать в выводе компилятора, в письме и в списке коммитов. Умеете применять макрос — примените его к результату grep. Умеете редактировать текст — значит, умеете редактировать список файлов в dired и одним C-c C-c переименовать сто файлов (режим wdired). Ни в одном редакторе, где каждая панель — отдельный виджет с собственными правилами, такое невозможно в принципе.

Командный цикл: что происходит при нажатии клавиши

Понимание этого цикла снимает 80% вопросов «почему у меня не работает биндинг».

Три следствия, которые стоит запомнить:

  1. Порядок keymap’ов объясняет конфликты. Ваш биндинг «не работает», потому что минорный мод перехватил клавишу раньше мажорного. Диагностика — C-h k (какая команда реально привязана) и C-h c. Лечение — биндить в keymap нужного уровня или использовать minor-mode с высоким приоритетом.
  2. pre-command-hook и post-command-hook вызываются после каждой команды — на них держатся подсветка скобок, авто-сохранение, индикаторы. И на них же легко посадить редактор: тяжёлая функция в post-command-hook даёт лаг при каждом движении курсора.
  3. Всё это — один поток. Lisp в Emacs выполняется однопоточно; make-thread из Emacs 26 даёт кооперативные, а не вытесняющие потоки. Значит, любая долгая синхронная операция замораживает интерфейс целиком. Это главная архитектурная слабость Emacs, и её не чинят конфигом — только асинхронными процессами (make-process, url-retrieve с колбэком).

Emacs Lisp за десять минут

Elisp — диалект Lisp 1970-х с современными заплатками. Он не Scheme и не Common Lisp: свой объектный слой (cl-defstruct, defclass из EIEIO), свой набор странностей. Но выучить рабочий минимум занимает вечер.

;;; my-utils.el --- личные утилиты  -*- lexical-binding: t; -*-

;; Переменные. defvar — глобальная с документацией, setq — присваивание.
(defvar my/project-root "~/work" "Корень рабочих проектов.")

;; Функция. Интерактивная = вызывается через M-x и биндится на клавишу.
(defun my/insert-iso-date (&optional with-time)
  "Вставить текущую дату в формате ISO. С префиксом — вместе со временем."
  (interactive "P")                       ; "P" = сырой префиксный аргумент C-u
  (insert (format-time-string (if with-time "%Y-%m-%dT%H:%M:%S%z" "%Y-%m-%d"))))

;; Буфер-локальные переменные — сердце модальности Emacs.
;; Одна и та же переменная имеет разные значения в разных буферах.
(add-hook 'python-ts-mode-hook
          (lambda ()
            (setq-local fill-column 88)   ; black-совместимая ширина
            (setq-local tab-width 4)))

;; Хуки — списки функций, вызываемых в определённый момент.
(add-hook 'before-save-hook #'delete-trailing-whitespace)

;; Замыкания работают только при lexical-binding: t (строка -*- выше).
;; Без неё Emacs использует динамическое связывание, и counter сломается.
(defun my/make-counter ()
  (let ((n 0))
    (lambda () (setq n (1+ n)) n)))

(provide 'my-utils)
;;; my-utils.el ends here

Пункт про lexical-binding — не педантизм. Динамическое связывание по умолчанию — самое неприятное наследие Elisp: функция видит переменные вызывающего кода, замыкания не захватывают окружение, отладка превращается в гадание. Emacs 30 уже выдаёт предупреждение, если файл открыт без магической строки. Всегда ставьте её первой строкой в каждом .el.

Обязательные привычки при работе с Elisp:

  • C-x C-e — выполнить выражение перед курсором. Изменили функцию — выполнили — она уже новая. Это REPL-driven разработка внутри редактора.
  • M-x ielm — полноценный REPL.
  • C-h f / C-h v / C-h k / C-h o — что делает функция, переменная, клавиша, символ. Это и есть «self-documenting»: документация не сайт, а часть образа в памяти.
  • M-x debug-on-entry, M-x edebug-defun — пошаговая отладка Lisp прямо в исходнике.

Конфигурация: как это должно выглядеть в 2026 году

Самая частая ошибка новичка — копипастить чужой init.el на 2000 строк. Второй по частоте — начинать с Doom или Spacemacs, не понимая базы, и через полгода не уметь починить сломавшееся. Рабочая схема — маленький осмысленный конфиг на use-package (встроен с Emacs 29).

early-init.el — выполняется до инициализации GUI и пакетной системы. Здесь живёт то, что влияет на старт:

;;; early-init.el --- до GUI и package.el  -*- lexical-binding: t; -*-

;; На время старта отключаем GC: сборка мусора при загрузке пакетов
;; съедает больше времени, чем экономит памяти. Вернём разумное значение потом.
(setq gc-cons-threshold most-positive-fixnum
      gc-cons-percentage 0.6)

;; Не рисовать элементы, которые мы всё равно выключим — экономит перерисовку.
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)

;; Не подставлять .el, если рядом лежит свежий .elc/.eln.
(setq load-prefer-newer t
      package-enable-at-startup t
      frame-inhibit-implied-resize t)

init.el — основное. Ниже конфиг, который реально можно взять как стартовый: он про редактирование, а не про красоту.

;;; init.el --- рабочая конфигурация  -*- lexical-binding: t; -*-

(require 'package)
(setq package-archives
      '(("gnu"    . "https://elpa.gnu.org/packages/")
        ("nongnu" . "https://elpa.nongnu.org/nongnu/")
        ("melpa"  . "https://melpa.org/packages/")))
(unless package-archive-contents (package-refresh-contents))

(require 'use-package)
(setq use-package-always-ensure t   ; ставить пакет, если его нет
      use-package-always-defer t)   ; грузить лениво — это главный рычаг старта

;; ---------- базовое поведение ----------
(setq-default indent-tabs-mode nil tab-width 4 fill-column 100)
(setq make-backup-files nil          ; бэкапы рядом с файлом мешают git
      auto-save-default t
      create-lockfiles nil
      require-final-newline t
      sentence-end-double-space nil
      use-short-answers t)           ; y/n вместо yes/no, Emacs 28+

(save-place-mode 1)                  ; вернуться на ту же строку при открытии
(recentf-mode 1)                     ; список недавних файлов
(global-auto-revert-mode 1)          ; подхватывать изменения с диска (git switch)
(delete-selection-mode 1)
(electric-pair-mode 1)
(global-so-long-mode 1)              ; не умирать на файлах с километровыми строками
(when (fboundp 'which-key-mode) (which-key-mode 1))

;; ---------- минибуфер: vertico + orderless + marginalia + consult ----------
;; Этот стек заменил тяжёлые helm/ivy: он опирается на штатный completing-read,
;; поэтому работает со ВСЕМИ командами Emacs, включая ещё не написанные.
(use-package vertico   :demand t :init (vertico-mode 1))
(use-package marginalia :demand t :init (marginalia-mode 1))
(use-package orderless
  :demand t
  :custom (completion-styles '(orderless basic))
          (completion-category-overrides '((file (styles basic partial-completion)))))
(use-package consult
  :bind (("C-s"   . consult-line)        ; поиск по буферу со списком совпадений
         ("C-x b" . consult-buffer)      ; буферы, файлы, закладки в одном списке
         ("M-g g" . consult-goto-line)
         ("M-s r" . consult-ripgrep)))   ; grep по проекту, потоковый
(use-package embark
  :bind (("C-." . embark-act)))          ; «что я могу сделать с этим объектом?»

;; ---------- дополнение в буфере ----------
(use-package corfu
  :demand t
  :init (global-corfu-mode 1)
  :custom (corfu-auto t) (corfu-auto-delay 0.15) (corfu-cycle t))

;; ---------- проекты и git ----------
(use-package project :ensure nil        ; штатный, не тянем из MELPA
  :bind ("C-x p g" . project-find-regexp))
(use-package magit
  :bind ("C-x g" . magit-status))

;; ---------- синтаксис: tree-sitter вместо регулярок ----------
(setq treesit-language-source-alist
      '((python     "https://github.com/tree-sitter/tree-sitter-python")
        (typescript "https://github.com/tree-sitter/tree-sitter-typescript"
                    "master" "typescript/src")
        (go         "https://github.com/tree-sitter/tree-sitter-go")))
;; Один раз: M-x treesit-install-language-grammar
(setq major-mode-remap-alist
      '((python-mode     . python-ts-mode)
        (go-mode         . go-ts-mode)
        (typescript-mode . typescript-ts-mode)))

;; ---------- LSP: eglot, встроен с Emacs 29 ----------
(use-package eglot :ensure nil
  :hook ((python-ts-mode go-ts-mode typescript-ts-mode) . eglot-ensure)
  :bind (:map eglot-mode-map
              ("C-c l r" . eglot-rename)
              ("C-c l a" . eglot-code-actions)
              ("C-c l f" . eglot-format-buffer))
  :config
  (setq eglot-autoshutdown t
        eglot-events-buffer-size 0)     ; логи протокола едят память и время
  (add-hook 'before-save-hook
            (lambda () (when (eglot-managed-p) (eglot-format-buffer)))))

;; ---------- GC обратно в разумные рамки ----------
(add-hook 'emacs-startup-hook
          (lambda ()
            (setq gc-cons-threshold (* 64 1024 1024)  ; 64 МБ вместо 800 КБ
                  gc-cons-percentage 0.2)
            (message "Старт за %.2f с, сборок мусора: %d"
                     (float-time (time-subtract after-init-time before-init-time))
                     gcs-done)))

Комментарий про gc-cons-threshold: значение по умолчанию — 800 000 байт, оно из эпохи, когда у машин было 32 МБ памяти. На современном железе редактор с таким порогом собирает мусор десятки раз в минуту, и это ощущается как микрозаикания при наборе. 32–64 МБ — здравый компромисс; ставить most-positive-fixnum навсегда не нужно, иначе редкая, но многосекундная пауза заменит частые короткие.

Eglot или lsp-mode

Оба живы, выбор реальный.

eglot lsp-mode
Поставка встроен с Emacs 29 MELPA
Философия минимализм, переиспользует штатные xref, flymake, eldoc собственный богатый UI
Зависимости нулевые десятки
Конфигурация почти не нужна много ручек
UI-плюшки нет, берёт из ядра lsp-ui, доктор, sideline, breadcrumbs
Скорость на больших проектах обычно легче тяжелее, но настраивается

Практика: начинайте с eglot. Если чего-то критически не хватает (например, нужны специфичные расширения протокола для Java через lsp-java) — переезжайте на lsp-mode. Обратный переезд случается чаще прямого.

Org-mode: почему это отдельная технология

Org-mode — plain-text формат и режим редактирования, написанный Карстеном Домиником в 2003 году и с тех пор входящий в поставку. Со стороны выглядит как Markdown с чекбоксами. На деле это три разные системы, соединённые общим синтаксисом: структурный редактор, система планирования и среда исполняемых документов.

#+TITLE: Рабочий план
#+STARTUP: overview

* Проект «Оплата»                                              :work:payments:
** TODO Починить идемпотентность вебхука               :bug:
   DEADLINE: <2026-07-24 Пт>
   :PROPERTIES:
   :Effort:   4h
   :JIRA:     PAY-1841
   :END:
   Дубли приходят, если провайдер ретраит быстрее, чем мы коммитим транзакцию.
   - [X] воспроизвести на стенде
   - [ ] добавить уникальный индекс по (provider, event_id)
   - [ ] бэкофилл дублей
** WAIT Согласовать лимиты с рисками
   :LOGBOOK:
   - State "WAIT" from "TODO" [2026-07-17 Пт 12:03] \\
     Ждём ответа команды рисков.
   :END:
** DONE Метрики по отказам
   CLOSED: [2026-07-15 Ср 18:20]

* Заметки
** Как считать SLA вебхуков
   Ссылка на тикет: [[https://jira.example.com/PAY-1841][PAY-1841]]

Файл читается глазами, кладётся в git, диффится по-человечески и при этом является базой данных: по нему строятся представления.

Рабочие процессы как конечный автомат

org-todo-keywords задаёт не просто метки, а состояния со смыслом: переход в некоторые из них логируется, некоторые считаются «завершёнными».

(use-package org
  :ensure nil
  :custom
  (org-directory "~/org")
  (org-agenda-files '("~/org/work.org" "~/org/personal.org"))
  (org-todo-keywords
   '((sequence "TODO(t)" "NEXT(n)" "DOING(o!)" "WAIT(w@/!)"
               "|" "DONE(d!)" "CANCELLED(c@)")))
  ;; (o!) — логировать время входа; (w@/!) — спросить причину и записать переход
  (org-log-into-drawer t)          ; журнал в :LOGBOOK:, чтобы не засорять текст
  (org-agenda-start-on-weekday nil)
  (org-startup-indented t)
  :bind (("C-c a" . org-agenda)
         ("C-c c" . org-capture))
  :config
  (setq org-capture-templates
        '(("t" "Задача в инбокс" entry
           (file+headline "~/org/work.org" "Инбокс")
           "* TODO %?\n  :PROPERTIES:\n  :CREATED: %U\n  :END:\n  %a\n")
          ("b" "Баг из кода" entry
           (file+headline "~/org/work.org" "Баги")
           ;; %a — ссылка на текущую позицию в файле, работает и для TRAMP
           "* TODO %? :bug:\n  %a\n  %i\n")
          ("j" "Журнал дня" entry
           (file+olp+datetree "~/org/journal.org")
           "* %U %?\n"))))

Ценность здесь не в TODO-списке — их пишут все. Ценность в org-capture: нажали C-c c b, находясь в строке кода, — задача создана со ссылкой на файл и строку, и вы вернулись туда, где были, через полторы секунды. Мысль не потеряна, контекст не переключён. Это тот случай, когда инструмент реально экономит внимание, а не время.

org-agenda собирает из всех файлов единый вид по датам, тегам и состояниям — по сути SQL-запрос к вашим текстовым файлам. Плюс org-clock-in/org-clock-out даёт учёт времени и отчёты (org-clock-report), который не требует ни одного стороннего сервиса.

Babel: исполняемые документы

org-babel позволяет вставлять блоки кода на трёх десятках языков, выполнять их прямо в документе и подставлять результат. Это literate programming в рабочем виде — подход описан в рецензируемой статье A Multi-Language Computing Environment for Literate Programming and Reproducible Research (Journal of Statistical Software, 2012).

#+NAME: fetch-orders
#+BEGIN_SRC sql :engine postgresql :dbhost replica.internal :database shop :results table
SELECT status, count(*) FROM orders WHERE created_at > now() - interval '1 day'
GROUP BY status ORDER BY 2 DESC;
#+END_SRC

#+RESULTS: fetch-orders
| status  | count |
|---------+-------|
| paid    |  8421 |
| pending |   312 |
| failed  |    47 |

#+BEGIN_SRC python :var data=fetch-orders :results output
# Результат SQL-блока приходит сюда как список списков — язык не важен.
total = sum(int(row[1]) for row in data)
for status, n in data:
    print(f"{status:>8}: {int(n)/total:6.2%}")
#+END_SRC

Три вещи, которые из этого получаются:

  1. Runbook, который выполняется. Инструкция по разбору инцидента с живыми запросами вместо «скопируйте вот этот SQL куда-нибудь».
  2. Литературная конфигурация. org-babel-tangle (C-c C-v t) вытаскивает блоки в реальные файлы: пишете config.org с объяснениями, получаете init.el, .zshrc, Dockerfile. Блоки помечаются :tangle ~/.config/foo/bar.conf.
  3. Экспорт. ox-* рендерит тот же документ в HTML, PDF через LaTeX, Markdown, reveal.js.

Честная оговорка: Jupyter выигрывает у Babel в интерактивности графики и в экосистеме, Quarto — в оформлении публикаций. Babel выигрывает в том, что документ остаётся plain text (нормальный diff в git), полиглотен по умолчанию и не требует ничего, кроме уже открытого редактора.

Практика продакшена: за что Emacs держат на работе

Есть набор вещей, которые в Emacs объективно сильнее, чем у конкурентов, и обычно именно они удерживают людей.

Magit. Интерфейс к git, который вместо «кнопок для частых операций» даёт прямое отображение модели git на буфер: staging отдельных строк одной клавишей s, интерактивный rebase, где строки переставляются как текст, разрешение конфликтов, reflog, bisect. Люди ставят Emacs исключительно ради Magit и работают в другом редакторе — это не шутка, это распространённый сценарий.

TRAMP. Прозрачная работа с удалёнными файлами: путь /ssh:deploy@prod-3:/etc/nginx/nginx.conf открывается как локальный, со всеми режимами, LSP и git. Многошаговые прыжки через бастион пишутся одной строкой:

;; Через jump-host на приватный хост, затем sudo — всё в одном пути:
;; C-x C-f /ssh:jump.corp|ssh:app-7.internal|sudo:app-7.internal:/var/log/app.log
(setq tramp-default-method "ssh"
      remote-file-name-inhibit-cache 60
      tramp-verbose 1)                    ; выше 3 — заметно медленнее
;; Ускорение: не искать проектные файлы и не запускать vc на удалённых путях
(setq vc-handled-backends '(Git))
(connection-local-set-profile-variables
 'remote-fast '((vc-handled-backends . nil)))
(connection-local-set-profiles
 '(:application tramp) 'remote-fast)

VS Code Remote SSH делает похожее, но иначе: он ставит серверную часть на удалённую машину. Это мощнее для полноценной разработки и невозможно там, где на хост нельзя ничего класть. TRAMP работает поверх обычного ssh и не оставляет следов.

Daemon и мгновенный старт. Долгий запуск Emacs — известная претензия. Ответ архитектурный: запускать один раз.

# systemd user unit идёт в поставке Emacs 26+
systemctl --user enable --now emacs

# В ~/.zshrc или ~/.bashrc:
export EDITOR="emacsclient -nw -a ''"   # -a '' поднимет демон, если он не запущен
export VISUAL="emacsclient -c -a ''"
alias e='emacsclient -n'                 # -n = не ждать закрытия буфера

Замер старта, который стоит сделать самому:

# Чистый Emacs без конфига — базовая линия
emacs -Q --eval '(message "%s" (emacs-init-time))' --batch
# Ваш конфиг
emacs --eval '(progn (message "%s" (emacs-init-time)) (kill-emacs))'
# Профилирование: какой пакет съел время
emacs -l ~/.emacs.d/init.el --eval '(profiler-start (quote cpu))'

Нативная компиляция. С Emacs 28 Lisp компилируется в машинный код через libgccjit (.eln-файлы). Проверить, что сборка её поддерживает: M-: (native-comp-available-p). Реальный выигрыш на нагруженном Lisp — обычно в 2–4 раза на конкретных операциях (сложные шрифт-локи, org-agenda на тысячах записей); на «печатании в буфер» разницы не почувствуете, потому что там узкое место — redisplay на C. Первый запуск после установки пакетов будет медленнее: идёт фоновая компиляция.

Evil: Vim внутри Emacs

Отдельно про то, что примиряет два лагеря. evil-mode — не эмуляция «на глазок», а честная реализация модального редактирования Vim, включая операторы, текстовые объекты, регистры и ex-команды. Про сам модальный подход подробно — в статье про основы Vim и продвинутом Vim.

(use-package evil
  :demand t
  :init
  (setq evil-want-keybinding nil     ; обязательно ДО загрузки, иначе конфликты
        evil-want-C-u-scroll t
        evil-undo-system 'undo-redo  ; штатный undo-redo из Emacs 28
        evil-respect-visual-line-mode t)
  :config
  (evil-mode 1))

(use-package evil-collection          ; evil-биндинги для magit, dired, ibuffer...
  :after evil
  :config (evil-collection-init))

(use-package general                  ; удобный SPC-лидер в стиле Doom/Spacemacs
  :after evil
  :config
  (general-create-definer my/leader
    :states '(normal visual) :prefix "SPC")
  (my/leader
    "f f" #'find-file
    "b b" #'consult-buffer
    "g g" #'magit-status
    "p r" #'consult-ripgrep))

Комбинация «модальное редактирование Vim + расширяемость Emacs» — вполне рациональный выбор: вы берёте лучшую известную грамматику команд и лучшую известную систему расширения. Именно на этом построены Doom Emacs и Spacemacs. Цена — дополнительный слой, который иногда конфликтует с пакетами, не знающими про evil; evil-collection закрывает большую часть, но не всё.

Честное сравнение

Критерий Emacs Neovim VS Code JetBrains
Язык расширения Emacs Lisp Lua TypeScript Kotlin/Java
Модель расширения переопределение изнутри плагины + встроенный Lua API + процесс-изолятор плагины через SDK
Правка поведения на лету да, полностью частично нет, требует reload нет
Порог входа очень высокий высокий низкий средний
Старт «из коробки» почти пустой почти пустой рабочий сразу рабочий сразу
LSP eglot / lsp-mode встроенный встроенный не нужен, свой анализ
Глубина рефакторингов средняя, зависит от LSP средняя средняя лучшая на рынке
Работа с git Magit, лучший в классе плагины хорошо хорошо
Удалённое редактирование TRAMP, без агента плагины Remote SSH с агентом Gateway с агентом
Отзывчивость UI однопоточный, может замирать быстрый обычно ровный тяжёлый, но параллельный
Заметки/планирование org-mode, вне конкуренции нет расширения нет
Сколько живёт конфиг десятилетия годы годы почти не нужен
Кто поддерживает GNU + сообщество сообщество Microsoft коммерческая компания

По распространённости честная картина такая: в опросах Stack Overflow последних лет Emacs стабильно держится в районе нескольких процентов — примерно на порядок ниже VS Code (см. Stack Overflow Developer Survey). Это не «умирание», а стабильная ниша; абсолютное число пользователей за десять лет скорее выросло вместе с отраслью.

Сколько это стоит

Числа ниже — оценка по типичному опыту, а не измерение, но порядок правильный:

Этап Реалистичные затраты
Пройти встроенный туториал C-h t 30–40 минут
Перестать думать о навигации и правках 2–4 недели ежедневной работы
Собрать свой конфиг, который не стыдно 10–20 часов, размазанных по месяцам
Начать писать elisp под свои задачи ещё 10–15 часов
Обслуживание после этого 1–2 часа в месяц, плюс всплеск при мажорном апдейте

Просадка производительности в первые 2–3 недели реальна и составляет десятки процентов. Если у вас идёт горящий релиз — это не то время, чтобы менять редактор. Общая логика оценки «окупится / не окупится» разобрана в обзоре трека.

Типичные ошибки

  1. Начать с чужого мегаконфига. Doom Emacs даёт отличный результат за час, но когда через полгода что-то сломается, вы будете отлаживать чужие 20 000 строк Lisp без базы. Разумный путь: месяц на голом Emacs с конфигом на 100 строк, потом решайте.
  2. Не поставить lexical-binding: t. Приводит к загадочным багам в замыканиях, которые вы будете искать часами.
  3. Тяжёлые функции в post-command-hook или after-change-functions. Мгновенный результат — лаг при наборе. Проверять через M-x profiler-start в режиме cpu.
  4. Синхронные вызовы внешних процессов. shell-command-to-string на медленной команде вешает весь редактор. Используйте make-process с фильтром или async-shell-command.
  5. use-package без :defer. Двадцать пакетов, загруженных жадно, превращают старт в пять секунд. Правило: :hook, :bind, :commands дают отложенную загрузку автоматически; :demand t ставится осознанно и редко.
  6. tramp-verbose больше 3 «для отладки» и забыть. Удалённые операции замедляются в разы.
  7. Игнорировать RSI. Аккорды с Control реально нагружают мизинец. Минимум — переназначить Caps Lock на Control на уровне ОС; дальше — evil-mode или god-mode.
  8. Хранить конфиг только локально. Emacs-конфиг — это ваш накопленный инструмент за годы; он должен лежать в git. Про организацию dotfiles и синхронизацию окружения — отдельная статья трека.

Так почему он всё ещё жив

Не из-за ностальгии. Три конкретные причины.

Стабильность интерфейса на масштабе десятилетий. Конфиг 2010 года по большей части работает сегодня. Ни один коммерческий редактор такого не обещает и обещать не может: у Atom, который был «будущим редакторов», архив на GitHub с 2022 года. Emacs пережил TECO, XEmacs, Atom и переживёт что-нибудь ещё — потому что у него нет владельца, которому он может надоесть.

Единая среда вместо набора приложений. Код, git, заметки, планирование, почта, shell, удалённые серверы — одни и те же клавиши, одни и те же способы поиска, одни и те же макросы. Экономия здесь не в скорости операций, а в отсутствии переключений контекста между несовместимыми интерфейсами.

Расширяемость, которая не упирается в API. Когда нужна нестандартная вещь — не «есть ли плагин», а «напишу за двадцать минут». Для человека, который годами решает узкие задачи в конкретной предметной области, разница накапливается.

И честная другая сторона: Emacs требует, чтобы вы стали его соавтором. Кто не хочет — проиграет VS Code по любому критерию первых месяцев. Это не универсальный инструмент, а инструмент для конкретного склада: для тех, кому приятно затачивать среду, и для тех, у кого задачи достаточно долгоживущие, чтобы заточка окупилась.

Мини-итог

  • Emacs — Lisp-машина: ~5% на C, остальное на Elisp, переопределяемом во время работы.
  • Единая абстракция «всё есть буфер» делает навыки переносимыми между кодом, git, почтой и shell.
  • Командный цикл и приоритет keymap’ов объясняют почти все проблемы с биндингами; однопоточность объясняет почти все проблемы с отзывчивостью.
  • Современная база 2026 года: use-package + vertico/corfu + eglot + treesit + magit; всё, кроме первых двух групп, уже в поставке.
  • Org-mode — три системы в одной: структурный редактор, планировщик и среда исполняемых документов.
  • Демон плюс emacsclient снимают вопрос времени старта; native-comp снимает часть вопросов скорости.
  • Стоимость входа реальна: недели до продуктивности и часы на конфиг. Окупается на долгом горизонте и при желании программировать свой инструмент.

Источники

Что дальше

Мы разобрали инструмент, который требует от вас максимума и отдаёт максимум. Следующая статья — про противоположный полюс: редактор, который работает сразу, ставится в два клика и именно поэтому занял рынок.

VS Code: архитектура, расширения, отладка, remote и devcontainers

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов