Конфигурация GNU Emacs

Emacs – текстовый редактор от сообщества GNU. Он расширяется при помощи языка Emacs Lisp и является одним из старейших текстовых редакторов, который используется многими и по сей день. Далее идёт моя конфигурация этого редактора.


Исходный код

Вклады

Конфигурация составлена из моего кода, а также кода из конфигураций других людей. Их настолько, что все перечислять я не смогу, но оставлю интересные конфигурации других людей, в которые я время от времени заглядываю.

Лицензия

Я выступаю против копирайта и интеллектуальной собственности в целом, поэтому стараюсь копирайт избегать. По этой причине свой код распространяю на правах общественного достояния вашей страны. Для этого используется лицензия CC0. Если кратко, то можете использовать без ограничений вплоть до полной приватизации всего контента. К сожалению за чужие сниппеты не могу ручаться, они распространяются по своим лицензиям и я их не указывал, впрочем всё равно никто это не проверяет и я бы не стал напрягаться из-за авторских прав на конфигурацию текстового редактора.

О конфигурации

Минимальная конфигурация Emacs. Пакеты ставлю через Guix или package.el (elpa и melpa), для разделения ответственности использую грамотное программирование при помощи org-mode.

Версия Emacs 28+.

  • В Guix пакет emacs-next
  • В FreeBSD пакет emacs-devel
  • В Ubuntu PPA emacs28 и пакет emacs28-native

Из программирования поддерживаются C, Java, Python, Common Lisp / Scheme, Haskell, Idris, Ocaml, Ruby, PHP, SML, SQL, Erlang, редактирование веб-шаблонов и различных форматов конфигураций. Какие-то языки в лучшей мере настроены, какие-то хуже, но есть почти все плюс-минус популярные.

Для систем контроля версий использую встроенный VC и в некоторых случаях Magit. Для входа в виртуальные среды используется Direnv.

Для ведения заметок использую методологию Zettelkasten при помощи org-roam. Для доступа к оффлайн википедии и другим ресурсам Kiwix клиент, который стучится на сервер, который стоит дома.

Для коммуникаций клиент для Telegram telega, для IRC erc и электронной почты gnus, для Mastodon mastodon.el.

Для системного администрирования имеют пакеты для работы с Debian, NixOS, Guix. Также есть поддержка Docker, Docker-compose: можно запускать/стопать контейнеры, а также залетать в них через TRAMP.

Для установки необходимо установить пакеты Emacs из ../../guix/user.scm и выполнить tangle файла при помощи M-x org-babel-tangle-file. В дальнейшем для развёртывания конфигурации можно использовать M-x config-tangle или M-x config-load.

Данная конфигурация доступна также в сети интернет по адресу, но так как сайт обновляется нечасто, то там может лежать старая версия: https://w96k.dev/emacs.html

Исходники конфигурации лежат на Sourcehut: https://git.sr.ht/~w96k/dotfiles/tree/master/item/emacs

Распространяется на условиях свободной лицензии GNU GPL v3, реиспользование одобряется пока распространяется под той же лицензией.

Пакеты

Данная конфигурация использует следующие пакеты:

Avy
Прыжки по тексту
Anaconda
Поддержка Python
Auctex
Поддержка Tex и LaTeX
Browse Kill Ring
Обзор буфера обмена
Haskell Mode
Поддержка языка Haskell
Idris Mode
Поддержка языка Idris
Magit
Интерфейс к git
Gitpatch
Создание патчей
Goto Cgh
Переход к последнему изменению в буфере
Debbuge
Интерфейс debbugs (репорт багов Debian)
Deft
Полнотекстовый поиск
Direnv
Изменение окружения при "входе" в директорию
Docker и Docker Compose
Моды для поддержки контейнерных технологий
Exec Path From Shell
Синхронизация PATH переменной из шелла
Expand Region
Семантическое расширение выделения на курсоре
Robe
Прыжки по определениям Ruby
Geiser
Repl для языка Scheme
Guix
Интерфейс к Guix
Org Roam
Zettelkasten заметки в org-mode
Gnuplot
Программное рисование графиков
Nix Mode
Поддержка языка Nix
Pdf Tools
Рендер PDF файлов в Emacs
PHP Mode
Поддержка языка PHP
Kiwix
Чтение скачанных архивов веб-страниц
SML Mode
Поддержка языка Standard ML
Sly/Slime
REPL языка Common Lisp
Simple HTTPD
HTTP Сервер на elisp
Telega
Интерфейс к Telegram
Treemacs
Сайдбар для навигации по проекту
Undo Tree
Визуализация дерева отмен
Yasnippet и Yasnippet Snippets
Сниппеты для текстов и ЯПов
Web Mode
Поддержка шаблонов HTML и прочего

Следующий блок нужен для тех, кто хочет использовать встроенный пакетный менеджер. Я использую Guix для большей части взаимодействий со сторонним кодом.

(require 'package)

(setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/")
                   ("melpa" . "http://melpa.org/packages/")))

(when (< emacs-major-version 27)
  (package-initialize))

(require 'gnutls)

(when (not package-archive-contents)
  (progn
    (package-refresh-contents)
    (package-install 'avy)
    ;; (package-install 'anzu)
    (package-install 'anaconda-mode)
    (package-install 'auctex)
    (package-install 'rmsbolt)
    ;; (package-install 'beginend)
    ;; (package-install 'composer)
    (package-install 'cinspect)
    (package-install 'debian-el)
    (package-install 'dpkg-dev-el)
    (package-install 'edebug-inline-result)
    ;;(package-install 'haskell-mode)
    ;;(package-install 'idris-mode)
    (package-install 'magit)
    (package-install 'git-timemachine)
    (package-install 'git-dwim)
    (package-install 'gitpatch)
    (package-install 'goto-chg)
    (package-install 'debbugs)
    ;;(package-install 'deft)
    ;; (package-install 'direnv)
    (package-install 'dumb-jump)
    (package-install 'docker)
    (package-install 'docker-cli)
    (package-install 'docker-tramp)
    (package-install 'docker-compose-mode)
    (package-install 'org-sql)
    ;;(package-install 'eglot)
    ;; (package-install 'eglot-java)
    ;; (package-install 'mastodon)
    ;; (package-install 'exec-path-from-shell)
    (package-install 'expand-region)
    ;; (package-install 'flymake-php)
    (package-install 'flycheck)
    (package-install 'flycheck-phpstan)
    ;; (package-install 'phpactor)
    ;;(package-install 'robe)
    (package-install 'geiser)
    (package-install 'geiser-guile)
    (package-install 'guix)
    ;; (package-install 'git-gutter)
    ;; (package-install 'gnuplot)
    ;;(package-install 'swiper)
    ;;(package-install 'sqlite3)
    (package-install 'org-roam)
    ;; (package-install 'org-roam-ui)
    ;; (package-install 'org-download)
    ;; (package-install 'org-babel-eval-in-repl)
    ;;(package-install 'ob-php)
    ;; (package-install 'on-screen)
    ;;(package-install 'nix-mode)
    (package-install 'pdf-tools)
    ;;(package-install 'phpactor)
    ;; (package-install 'company-quickhelp)
    ;;(package-install 'ac-php)
    (package-install 'php-mode)
    (package-install 'php-quickhelp)
    (package-install 'phan)
    ;;(package-install 'php-cs-fixer)
    ;; (package-install 'company-php)
    ;;(package-install 'php-eldoc)
    ;;(package-install 'phps-mode)
    (package-install 'realgud)
    (package-install 'realgud-xdebug)
    ;; (package-install 'kiwix)
    ;;(package-install 'sml-mode)
    (package-install 'sly)
    (package-install 'simple-httpd)
    (package-install 'sudo-edit)
    ;;(package-install 'treemacs)
    (package-install 'undo-tree)
    ;; (package-install 'yasnippet)
    ;; (package-install 'yasnippet-snippets)
    ;; (package-install 'vimrc-mode)
    ;; (package-install 'flymake-phpcs)
    ;; (package-install 'flymake-phpstan)
    ;; (package-install 'no-littering)
    (package-install 'web-mode)
    ;; (package-install 'which-key)
    ;; (package-install 'reverse-im)
    (package-install 'imenu-list)
    ;; (package-install 'isearch-mb)
    (package-install 'visual-fill-column)
    ;; (package-install 'browse-kill-ring)
    (package-install 'corfu)
    ;; (package-install 'inf-ruby)
    (package-install 'yaml-mode)
    ;; (package-install 'geben)
    (package-install 'link-hint)
    (package-install 'whole-line-or-region)
    (package-install 'quickrun)
    ;; (package-install 'psysh)
    ;; (package-install 'restclient)
    ))

Инициализация

Следующие блоки кода выводят в файл early-init.el

Компиляция

JIT компиляция elisp кода в машинный

(when (eq window-system 'pgtk)
  (pgtk-use-im-context t))

(when (fboundp 'native-compile-async)
    (setq comp-async-compilation t
      package-native-compile t

      native-comp-speed 2
      native-comp-async-jobs-number 1
      comp-num-cpus 2
      ;; JIT Compilation
      native-comp-deferred-compilation t
      ;; AOT Compilation
      package-native-compile t
      comp-async-report-warnings-errors nil))
  • Ручная компиляция
    ;; (native-compile-async "~/.emacs.d/elpa/" 'recursively)
    (native-compile-async "~/.guix-profile/share/emacs/site-lisp" 'recursively)
    
    ;; block until native compilation has finished
      (while (or comp-files-queue
                 (> (comp-async-runnings) 0))
    

Отключаем ненужные загрузки

;; Disable guix autoloading and x resources loading
(setq site-run-file nil)

EXWM

(require 'exwm)
(require 'exwm-config)
(exwm-config-example)

Редакируем GUI

Удаляем ненужные бары, меняем шрифт и модлайн. Использую дефолтный для шрифт DeJavu или недефолтный Agave, так как он является одним из самых интернациональных шрифтов по количеству доступных символов после Unifont. Unifont я не стал использовать, потому что иксы замыливают этот пиксельный шрифт, что делает его использование крайне неприятным.

;; Change mode-line-modes to show only major mode
(defvar mode-line-major-mode
  (let ((recursive-edit-help-echo "Recursive edit, type C-M-c to get out"))
    (list (propertize "%[" 'help-echo recursive-edit-help-echo)
          `(:propertize ("" mode-name)
                        help-echo "Major mode\n\
    mouse-1: Display major mode menu\n\
    mouse-2: Show help for major mode\n\
    mouse-3: Toggle minor modes"
                        mouse-face mode-line-highlight
                        local-map ,mode-line-major-mode-keymap)
          '("" mode-line-process)
          (propertize "%n" 'help-echo "mouse-2: Remove narrowing from buffer"
                      'mouse-face 'mode-line-highlight
                      'local-map (make-mode-line-mouse-map
                                  'mouse-2 #'mode-line-widen))
          (propertize "%]" 'help-echo recursive-edit-help-echo)
          " "))
  "Mode line construct for displaying major and minor modes.")

(put 'mode-line-major-mode 'risky-local-variable t)

;; Change mode-line-format
(setq-default mode-line-format
              '("%e"
                mode-line-front-space
                mode-line-mule-info

                mode-line-client
                mode-line-modified
                mode-line-remote
                mode-line-frame-identification
                ;; long-path
                mode-line-buffer-identification
                mode-line-misc-info
                "   "
                mode-line-major-mode
                " "
                vc-mode
                "  "

                mode-line-position
                mode-line-end-spaces
                ))
;; Do not resize the frame at this early stage.
(setq frame-inhibit-implied-resize t)
(setq use-dialog-box nil)

;;; Disable some gui
(scroll-bar-mode -1)
(tool-bar-mode -1)
(menu-bar-mode -1)
;;(tooltip-mode -1)

;;; Changing emacs default setting through customize
(custom-set-variables
 '(fill-column 72)
 '(git-gutter:added-sign " ")
 '(git-gutter:deleted-sign " ")
 '(git-gutter:modified-sign " ")
 '(git-gutter:unchanged-sign " ")
 '(lsp-headerline-breadcrumb-enable nil)
 '(scroll-bar-mode 'nil)
 '(scroll-bar-adjust-thumb-portion nil)
 '(tool-bar-position 'bottom)
 '(tool-bar-style 'both-horiz))

(custom-set-faces
 '(default ((t (:height 140 :family "Jetbrains Mono" :embolden t))))
 '(region ((t (:background "gray85"))))
 '(mode-line ((t (:background "grey70" :foreground "grey10"))))
 '(mode-line-inactive ((t (:inherit mode-line :background "grey90" :foreground "grey20" :box (:line-width (-2 . -2) :color "grey85") :weight light))))
 '(mode-line-buffer-id ((t (:weight bold))))
 '(mode-line-emphasis ((t (:weight bold))))
 '(lsp-modeline-code-actions-face ((t :inherit mode-line :height 100)))
 '(tool-bar ((t (:background "grey80" :foreground "grey10"))))
 '(fill-column-indicator ((t (:foreground "grey95"))))
 '(hl-line ((t (:background "gray95"))))
 '(fringe ((t (:background "grey87"))))
 '(header-line ((t (:inherit mode-line :background "grey90"))))
 '(vertical-border ((t (:foreground "grey90"))))
 '(window-divider ((t (:foreground "gray90")))))

(set-fringe-style (cons 7 7))

(defun switch-gui ()
  "Disable/enable menu-bar and tool-bar."
  (interactive)
  (if menu-bar-mode
      (progn
        (menu-bar-mode -1)
        (tool-bar-mode -1)
        (scroll-bar-mode -1))
    (progn
      (menu-bar-mode 1)
      (tool-bar-mode 1)
      (scroll-bar-mode 1))))

(defun switch-scroll-bar ()
  "Disable/enable scroll-bar."
  (interactive)
  (if scroll-bar-mode
      (scroll-bar-mode -1)
    (scroll-bar-mode)))

(define-key global-map (kbd "<f5>") 'switch-gui)
(define-key global-map (kbd "<f8>") 'switch-scroll-bar)

(provide 'early-init)
    ;;; early-init.el ends here

Инициализируем остальной конфиг

Следующие блоки кода выводят в файл init.el.

;; -*- lexical-binding: t -*-

;; Show/Hide errors
;; (setq debug-on-error nil)
;; (setq debug-on-quit nil)

(require 'package)

(defun package-loaded? (string)
  (if (or (cl-member string package-activated-list :test #'string=)
          (ignore-errors (load (concat string "-autoloads"))))
      t
      (progn
        (message (concat "Package " string " is not loaded"))
        nil)))

;; Timer
(add-hook 'emacs-startup-hook
          (lambda ()
            (message
             "Emacs ready in %s with %d garbage collections."
             (format "%.2f seconds"
                     (float-time
                      (time-subtract after-init-time before-init-time)))
             gcs-done)))

;; Dont ask when following symlinks
(setq vc-follow-symlinks t)

;; Load your custom settings
(setq custom-file "~/.emacs.d/custom-settings.el")
(load custom-file t)

Meta

Обо мне

;; Information about me
(setq user-full-name "Mikhail Kirillov"
      user-mail-address "w96k@runbox.com")

Конфигурация

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

(setq config-dotfiles-path "~/projects/dotfiles/emacs/.emacs.d/"
      config-path "~/.emacs.d/"
      config-name ".emacs-config.org")

(defun config-visit ()
  (interactive)
  (find-file (concat config-path config-name)))

(defun config-tangle ()
  (interactive)
  (org-babel-tangle-file (concat config-dotfiles-path config-name))
  ;; Configuration stored in another directory,
  ;; so I need to move files .emacs.d manually
  ;; (rename-file (concat config-dotfiles-path "early-init.el") config-path t)
  (rename-file (concat config-dotfiles-path "init.el") config-path t))

(defun config-load ()
  (interactive)
  (org-babel-load-file (concat config-dotfiles-path config-name) t))

Внешний вид

Отображение номера строк и пробелов

Изначально они отключены, но можно вызвать по клавише F7.

(define-key global-map
  (kbd "<f7>") 'global-display-line-numbers-mode)

(define-key global-map
  (kbd "<f6>") 'whitespace-mode)

Редактирование

Файловый менеджер

;; Show files in KiB
(setq dired-listing-switches "-hlap"
      dired-kill-when-opening-new-dired-buffer t)

(customize-set-variable 'global-auto-revert-non-file-buffers t)
(global-auto-revert-mode 1)

Линтер

Использую встроенный Flymake и Flycheck

  • Flymake
    ;;(add-hook 'prog-mode-hook 'flymake-mode)
    
    (require 'psalm)
    
    (define-prefix-command 'flymake-map)
    (global-set-key (kbd "C-q") 'flymake-map)
    (define-key flymake-map (kbd "n") 'flymake-goto-next-error)
    (define-key flymake-map (kbd "p") 'flymake-goto-prev-error)
    (define-key flymake-map (kbd "l") 'flymake-show-diagnostics-buffer)
    (define-key flymake-map (kbd "e") 'flymake-show-diagnostic)
    
  • Flycheck
    ;; (require 'psalm)
    
    (when (package-loaded? "flycheck")
    
      (defun flycheck-phanclient-start-daemon ()
        "Start the phan daemon"
        (interactive)
        (let* ((default-directory (php-project-get-root-dir))
               (phan-executable (or flycheck-phanclient--phan-executable
                                    (if (file-exists-p "vendor/bin/phan")
                                        (concat default-directory "vendor/bin/phan")
                                      (executable-find "phan"))))
               (cmd (list phan-executable "--daemonize-tcp-port" "4846" "--quick")))
          (apply #'start-process "PhanDaemon" "*phan daemon*" cmd)))
    
      (flycheck-define-checker php-phanclient
        "Phan"
        :command ("phan_client" "-l" source-original "-f" source)
        :error-patterns
        ((warning line-start (or "Parse" "Fatal" "syntax" "Phan") " error" (any ":" ",") " " (message) " in " (file-name) " on line " line line-end))
        :modes (php-mode php+-mode))
    
      (add-to-list 'flycheck-checkers 'php-phanclient)
    
      (flycheck-add-next-checker 'php '(warning . php-phanclient))
    
      (add-hook 'prog-mode-hook 'flycheck-mode))
    

Дерево проекта

Возможно в дальнейшем откажусь от этого пакета, так как по факту им пользуюсь нечасто. Он предоставляет дерево проектов, как в IDE.

(when (package-loaded? "treemacs")
  (progn
    (setq treemacs-width       50
          treemacs-show-cursor t
          treemacs-position    'right
          treemacs-indentation 1
          treemacs-tag-follow-mode t
          treemacs-fringe-indicator-mode nil)

    (define-key global-map
      (kbd "C-x C-d") 'treemacs)))

Дерево imenu

  ;; (when (package-loaded? "imenu-list")
    (setq imenu-list-focus-after-activation nil
          imenu-list-auto-resize nil
          imenu-list-mode-line-format '()
          imenu-list-size 0.4)
    (global-set-key (kbd "C-x C-d") #'imenu-list-smart-toggle)
;; )

Better Isearch

(when (package-loaded? "isearch-mb")
  (progn
    (isearch-mb-mode t)
    (global-set-key (kbd "C-s") 'isearch-forward-regexp)
    (global-set-key (kbd "C-r") 'isearch-backward-regexp)))

Система контроля версий

Модуль VC + Magit.

Operation VC Magit
Project status project-vc-dir (C-x p v) magit-status (C-x g)
Pull vc-update (F, in my case) magit-pull (F p)
New branch vc-retrieve-tag (C-u B s) magit-branch (b c)
Commit vc-next-action (C-x v v) magit-commit (c c)
Rebase shell-command (M-!) + git rebase master magit-rebase (r p)
Push vc-push (P or C-u P) magit-push (P p)
Stash mu-vc-git-stash (z) magit-stash (z)
Log vc-print-root-log (L) magit-log (l l)

https://www.manueluberti.eu//emacs/2021/11/27/vc/

  (setq vc-command-messages t)

  (global-set-key "\C-xvB" 'git-branch-next-action)

  ;; Use magit only when built-in VC fails
  (when (package-loaded? "magit")
    (progn
      (setq magit-refresh-status-buffer nil)
      (global-set-key (kbd "C-x g") 'magit-status)))

(package-loaded? "git-timemachine")

Прыжки

(when (package-loaded? "avy")
  (progn
    (define-key global-map (kbd "M-s M-s") 'avy-goto-char)
    (define-key global-map (kbd "M-s s") 'avy-goto-char)
    (define-key global-map (kbd "M-s g") 'avy-goto-line)
    (define-key global-map (kbd "M-s l") 'avy-goto-char-in-line)
    (define-key global-map (kbd "M-s M-l") 'avy-goto-char-in-line)

    ;; Rewrite default bind to avy
    (define-key global-map (kbd "M-g g") 'avy-goto-line)
    (define-key global-map (kbd "M-g M-g") 'avy-goto-line)))

(when (package-loaded? "link-hint")
  (progn
    (define-key global-map (kbd "M-s j") 'link-hint-open-link)))

;; Прыжок на последнее изменение
(when (package-loaded? "goto-chg")
  (progn
    (setq glc-default-span 2)
    (define-key global-map (kbd "C-z") 'goto-last-change)
    (define-key global-map (kbd "M-z") 'goto-last-change-reverse)))

;; Dumb Jump
(when (package-loaded? "dumb-jump")
  (progn
    (define-key global-map (kbd "C-.") 'dumb-jump-go)))

Проекты

Использую встроенный project.el I use built-in project.el

Ограничение ширины строки

(when (package-loaded? "visual-fill-column")
  (progn
    ;;; Column width limit highlighter
    (add-hook 'prog-mode-hook 'display-fill-column-indicator-mode)

    ;;; Set column width to 79 according to pep8 for python
    (add-hook 'python-mode-hook
              (lambda () (set-fill-column 79)))))

Подсвечивание парных скобок

Ввод парных скобок и кавычек (electric)

;;; Input of pair delimiters
(electric-pair-mode)
(add-hook 'prog-mode-hook 'electric-pair-mode)
;; (add-hook 'prog-mode-hook 'rainbow-identifiers-mode)

Kill-ring

(when (package-loaded? "browse-kill-ring")  
  (define-key global-map (kbd "C-M-y") 'browse-kill-ring))

Tags

Для прыжков и поиска функций/классов и т.д.

(setq path-to-ctags "~/.guix-profile/bin/ctags")

(defun tags-create (dir-name)
  "Create tags file."
  (interactive "DDirectory: ")
  (shell-command
   (format "%s -f TAGS -e -R %s" path-to-ctags
           (directory-file-name dir-name))))

(defun tags-create-python (dir-name)
  "Create tags with python interpreter"
  (interactive "DDirectory: ")
  (shell-command
   (format "%s -f TAGS -e -R --fields=+l --languages=python --python-kinds=-iv $(python -c \"import os, sys; print(' '.join('{}'.format(d) for d in sys.path if os.path.isdir(d)))\") %s" path-to-ctags
           (directory-file-name dir-name))))  

Дополнение

Дебаггер

(when (package-loaded? "realgud")
  (load "~/.emacs.d/site-lisp/realgud-xdebug/realgud-xdebug.el"))
(when (package-loaded? "geben")
  (setq geben-dbgp-default-port 9003))
  • Автодополнение кода и документация

    По большей части я использую дефолтный Completion Buffer и Corfu

    (when (package-loaded? "corfu")
      (progn
        (setq corfu-preview-current 'nil
              corfu-popupinfo-delay t)
        (corfu-mode 1)
        (corfu-popupinfo-mode 1)
        (defun show-default-completion-buffer ()
          (interactive)
          (corfu-quit)
          (corfu-mode -1)
          (completion-at-point)
          (corfu-mode 1)
          (corfu-popupinfo-mode 1))
        (define-key corfu-map (kbd "M-TAB") 'show-default-completion-buffer)
        (define-key corfu-map (kbd "TAB") 'show-default-completion-buffer)
        (define-key corfu-map (kbd "C-M-i") 'show-default-completion-buffer)      
        (corfu-mode -1)
        (add-hook 'prog-mode-hook 'corfu-mode)
    
        (defun corfu-send-shell (&rest _)
          "Send completion candidate when inside comint/eshell."
          (cond
           ((and (derived-mode-p 'eshell-mode) (fboundp 'eshell-send-input))
            (eshell-send-input))
           ((and (derived-mode-p 'comint-mode)  (fboundp 'comint-send-input))
            (comint-send-input))))
    
        (advice-add #'corfu-insert :after #'corfu-send-shell)
    
        (add-hook 'eshell-mode-hook 'corfu-mode)))
    
    
  • Модификация дефолта
    (setq completion-styles '(basic partial-completion substring flex emacs22)
          completion-ignore-case t
          read-buffer-completion-ignore-case t
          read-file-name-completion-ignore-case t)
    
  • Агрессивный дефолтный комплит
    (setq aggressive-completion-delay 0.5)
    (aggressive-completion-mode t)
    

Полнотекстовый поиск

Для выхода из поиска – C-c C-q

(load "deft-autoloads")

(define-key global-map
  (kbd "C-c n s") 'deft)

(setq deft-recursive t
      deft-use-filter-string-for-filename t
      deft-default-extension "org md"
      deft-directory "~/projects/at-w96k/content/digarden")

Визуализирование откатов

При помощи пакета undo-tree

(when (package-loaded? "undo-tree")
  (progn
    (add-hook 'prog-mode-hook #'undo-tree-mode)
    (add-hook 'org-mode-hook #'undo-tree-mode)
    (setq undo-tree-auto-save-history nil)))

Сниппеты

(when (package-loaded? "yasnippet")
  (progn
    (add-hook 'prog-mode-hook #'yas-minor-mode)))

Клиент LSP

(with-eval-after-load 'eglot
  (add-to-list 'eglot-server-programs '((php-mode phps-mode) . ("~/projects/phpactor/bin/phpactor" "language-server" "-vvv")))
  (add-to-list 'eglot-server-programs '((php-mode phps-mode) . ("intelephense" "--stdio")))

  ;; No event buffers, disable providers cause a lot of hover traffic. Shutdown unused servers.
  (setq eglot-events-buffer-size 0
        eglot-ignored-server-capabilities '(:hoverProvider
                                            :documentHighlightProvider)
        eglot-autoshutdown t))

;; Show all of the available eldoc information when we want it. This way Flymake errors
;; don't just get clobbered by docstrings.        
(add-hook 'eglot-managed-mode-hook
          (lambda ()
            "Make sure Eldoc will show us all of the feedback at point."
            (setq-local eldoc-documentation-strategy
                        #'eldoc-documentation-compose)))

Линтеры

(defun my-php-mode-setup ()
  "My PHP-mode hook."
  (require 'flycheck-phpstan)
  (flycheck-mode t))

(add-hook 'php-mode-hook 'my-php-mode-setup)
;; (add-hook 'php-mode-hook 'flymake-php-load)
 ;; (add-hook 'php-mode-hook 'flymake-phpstan-turn-on)

  ;; (require 'flycheck-phpstan)


;;(add-to-list 'auto-mode-alist '("\\.\\(php\\|phtml\\)\\'" . phps-mode))

;; (phps-mode-flycheck-setup)

;; (setq phps-mode-async-process t)
;; (setq phps-mode-async-process-using-async-el t)

Выделение

(when (package-loaded? "expand-region")
  (global-set-key (kbd "C-=") 'er/expand-region))

Сессия

(desktop-save-mode 1)

Скроллинг

(setq scroll-margin 0)

Поиск

  • Isearch
    (with-eval-after-load 'isearch
      (define-key isearch-mode-map "\C-h" 'isearch-delete-char)
      (define-key isearch-mode-map "\C-ch" 'isearch-help-for-help))
    
  • Подсчёт кандидатов
    (global-anzu-mode t)
    
  • Swiper (не используется)
    (load "swiper-autoloads")
    (global-set-key (kbd "C-s") 'swiper)
    
    (setq swiper-include-line-number-in-search t
          swiper-use-visual-line t
          swiper-stay-on-quit t)
    

Подсказка биндов

Пакет Which-key

(load "which-key-autoloads")
(which-key-setup-side-window-right)
(which-key-mode)

(setq which-key-side-window-max-width 0.5
      which-key-show-remaining-keys t
      which-key-max-display-columns 50
      which-key-max-description-length 35
      which-key-sort-order 'which-key-local-then-key-order
      which-key-idle-delay 0.25)

Права суперпользователя

Sudo-edit

(package-loaded? "sudo-edit")

Промежуточный код

Показывает собранное состояние будь то собранный куски на ассемблере или байт-код при помощи пакета RMSbolt.

(add-hook 'prog-mode-hook 'rmsbolt-mode)

Быстрый запуск программы

(when (package-loaded? "quickrun")
  (define-key global-map (kbd "C-c C-c") 'quickrun))

Языки программирования

Common Lisp

  • REPL
    (load "sly-autoloads")
    
    (setq sly-lisp-implementations
          '((clisp ("clisp"))
            (cmucl ("cmucl" "-quiet"))
            (sbcl ("/opt/sbcl/bin/sbcl") :coding-system utf-8-unix)))
    

Erlang

(load "erlang-autoloads")

Ruby

(when (package-loaded? "inf-ruby")
  (add-hook 'ruby-mode-hook 'inf-ruby-minor-mode))

(when (package-loaded? "inf-ruby")
  (add-hook 'ruby-mode-hook 'robe-mode))

Scheme

(setq geiser-active-implementations '("guile"))

Python

  • Автодополнение и линт
    (when (package-loaded? "anaconda-mode")
      (progn
        (add-hook 'python-mode-hook 'anaconda-mode)
        (add-hook 'python-mode-hook 'anaconda-eldoc-mode)))
    
    ;; (when (load "flymake" t)
    ;;   (defun flymake-pylint-init ()
    ;;     (let* ((temp-file (flymake-init-create-temp-buffer-copy
    ;; 		       'flymake-create-temp-inplace))
    ;; 	   (local-file (file-relative-name
    ;; 			temp-file
    ;; 			(file-name-directory buffer-file-name))))
    ;;       (list "epylint" (list local-file))))
    
    ;;   (add-to-list 'flymake-allowed-file-name-masks
    ;; 	       '("\\.py\\'" flymake-pylint-init)))
    
    ;; (add-hook 'python-mode-hook 'flymake-mode)
    
  • Прыжки в функции стандартной библиотеки на си

SML

(add-hook 'sml-mode-hook 'sml-mode)

PHP

  • PHP-Mode

    Необходимо скачать и распаковать мануал PHP (в формате html) в директорию ~/.emacs.d/php-manual/.

    ;; (add-to-list 'load-path "~/.emacs.d/site-lisp/realgud-xdebug/")
    ;; (require 'realgud-xdebug)
    
    ;; (defun init-php-mode ()
    ;;   (eglot-ensure))
    
    (with-eval-after-load 'php-mode
      (custom-set-variables '(lsp-phpactor-path "/home/w96k/php/phpactor"))
      ;; (add-hook 'php-mode-hook #'init-php-mode)
      )
    
    (when (package-loaded? "php-mode")
      (progn
        (add-hook 'php-mode-hook 'php-enable-symfony2-coding-style)
        (setq lsp-intelephense-php-version "8.1.16")
        (defvar phpactor-executable "~/.local/bin/phpactor")
        (custom-set-variables '(lsp-phpactor-path "~/usr/local/bin/phpactor"))
    
        (add-hook 'php-mode-hook 
                  '(lambda ()
                     ;; (require 'yasnippet)
                     ;; (require 'yasnippet-snippets)
    
                     (set-fill-column 120)
    
                     ;; (make-local-variable 'eldoc-documentation-function)
                     ;; (setq eldoc-documentation-function
                     ;;       'phpactor-hover)
                     ;; (yas-minor-mode t)
                     (define-key php-mode-map (kbd "C-c h") 'php-quickhelp-at-point)))
    
        (setq php-manual-path
              "~/php/php-manual/"
              php-quickhelp-dir "~/php/php-manual/"
              php-quickhelp--dest "~/.emacs.d/php-manual/php_manual_en.json")
    
    
        ;; (add-hook 'php-mode-hook 
        ;; 		'(lambda ()
        ;; 		   ;; (auto-complete-mode t)
    
        ;; 		   ;; (require 'ac-php)
        ;; 		   (require 'php-quickhelp)
        ;; 		   (require 'company)
        ;; 		   (company-mode t)
        ;; 		   (require 'company-php)
        ;; 		   (require 'company-quickhelp)
    
        ;; 		   (require 'yasnippet)
        ;; 		   (require 'yasnippet-snippets)
    
        ;; 		   (set (make-local-variable 'company-backends)
        ;; 			'((company-ac-php-backend company-dabbrev-code)
        ;; 			  php-quickhelp-company-php
        ;; 			  company-capf company-files))
    
        ;; 		   (company-quickhelp-mode t)
    
        ;; 		   (define-key php-mode-map (kbd "C-M-i") 'company-complete)
        ;; 		   (define-key company-mode-map (kbd "M-TAB") 'company-complete)
    
        ;; 		   ;; (setq ac-sources '(ac-source-php php-quickhelp-company-php))
        ;; 		   ;; (setq eldoc-documentation-function
        ;; 		   ;;       'php-quickhelp-eldoc-func)
    
        ;; 		   (yas-minor-mode t)
    
        ;; 		   ;; (define-key php-mode-map (kbd "C-M-i") 'auto-complete)
        ;; 		   ;; (define-key ac-mode-map (kbd "M-TAB") 'auto-complete)
    
        ;; 		   (define-key php-mode-map (kbd "C-c H")
        ;; 			       'php-local-manual-search)
    
        ;; 		   (define-key php-mode-map (kbd "C-c h") 'php-quickhelp-at-point)
        ;; 		   (define-key company-mode-map (kbd "C-c h") 'php-quickhelp-at-point)
    
        ;; 		   ;; (define-key php-mode-map (kbd "C-c t") 'ac-php-show-tip)
    
        ;; 		   ;; Jump to definition (optional)
        ;; 		   (define-key php-mode-map
        ;; 			       (kbd "M-.") 'ac-php-find-symbol-at-point)
    
        ;; 		   ;; Return back (optional)
        ;; 		   (define-key php-mode-map
        ;; 			       (kbd "M-,") 'ac-php-location-stack-back)))
        ))
    
  • Composer
  • Flymake PHP
  • REPL
  • LSP сервер

    PHPactor

    (with-eval-after-load 'php-mode
      (define-key php-mode-map (kbd "M-.") #'phpactor-goto-definition)
      (define-key php-mode-map (kbd "M-?") #'phpactor-find-references))
    
  • Transient меню
    (require 'transient)
    (define-transient-command php-menu ()
      "Php"
      [["Class"
        ("cc" "Copy" phpactor-copy-class)
        ("cn" "New" phpactor-create-new-class)
        ("cr" "Move" phpactor-move-class)
        ("ci" "Inflect" phpactor-inflect-class)
        ("n"  "Namespace" phpactor-fix-namespace)]
       ["Properties"
        ("a"  "Accessor" phpactor-generate-accessors)
        ("pc" "Constructor" phpactor-complete-constructor)
        ("pm" "Add missing props" phpactor-complete-properties)
        ("r" "Rename var locally" phpactor-rename-variable-local)
        ("R" "Rename var in file" phpactor-rename-variable-file)]
      ["Extract"
        ("ec" "constant" phpactor-extract-constant)
        ("ee" "expression" phpactor-extract-expression)
        ("em"  "method" phpactor-extract-method)]
      ["Methods"
        ("i" "Implement Contracts" phpactor-implement-contracts)
        ("m"  "Generate method" phpactor-generate-method)]
      ["Navigate"
        ("x" "List refs" phpactor-list-references)
        ("X" "Replace refs" phpactor-replace-references)
        ("."  "Goto def" phpactor-goto-definition)]
      ["Phpactor"
        ("s" "Status" phpactor-status)
        ("u" "Install" phpactor-install-or-update)]])
    

Языки декларирования

SQL

Need to install lsp-server called sqls https://emacs-lsp.github.io/lsp-mode/page/lsp-sqls/

;; Empty for now (was using emacsql)
(setq lsp-sqls-server "~/go/bin/sqls")

;; (setq lsp-sqls-workspace-config-path nil)

(setq lsp-sqls-connections
      '(((driver . "mysql") (dataSourceName . "dbuser:mangoworms@tcp(localhost:3306)/profile24"))))

The main way to interact with SQL is using org-mode

(when (package-loaded? "org-sql")
  (setq org-sql-files "~/projects/profile24/org"))

(add-hook 'sql-interactive-mode-hook
        (lambda ()
          (sql-connect "profile24")
          (toggle-truncate-lines t)))

(setq sql-connection-alist
    '((profile24
       (sql-product 'mysql)
       (sql-server "localhost")
       (sql-user "dbuser")
       (sql-password "123456")
       (sql-database "testdb")
       (sql-port 3306))))

Веб шаблоны

  • Web-mode
    (when (package-loaded? "web-mode")
      (progn
    
        (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
        (add-to-list 'auto-mode-alist '("\\.twig.html\\'" . web-mode))
    
        (setq web-mode-markup-indent-offset 2)
        (setq web-mode-enable-auto-pairing t)
        (setq web-mode-enable-css-colorization t)
        (setq web-mode-enable-block-face t)
        (setq web-mode-enable-current-element-highlight t)))
    

Org

  • Org-mode
    (org-babel-do-load-languages
     'org-babel-load-languages
     '((R . t)
       (ditaa . t)
       (dot . t)
       ;; (php . t)
       (emacs-lisp . t)
       (gnuplot . t)
       (haskell . nil)
       (latex . t)
       ;;(ledger . t)
       (ocaml . nil)
       (octave . t)
       (python . t)
       (ruby . t)
       (screen . nil)
       (shell . t)
       (sql . t)
       (js . t)))
    
    (defun org-babel-edit-prep:sql (babel-info)
      (setq-local buffer-file-name (->> babel-info caddr (alist-get :tangle)))
      (setq-local lsp-buffer-uri (->> babel-info caddr (alist-get :tangle) lsp--path-to-uri))
      (setq-local lsp-headerline-breadcrumb-enable nil)
      (lsp))
    
    (global-set-key (kbd "C-c l") 'org-store-link)
    (global-set-key (kbd "C-c a") 'org-agenda)
    (global-set-key (kbd "C-c c") 'org-capture)
    ;; (global-set-key (kbd "M-f") 'org-metaright)
    ;; (global-set-key (kbd "M-b") 'org-metaleft)
    ;; (global-set-key (kbd "M-p") 'org-metaup)
    ;; (global-set-key (kbd "M-n") 'org-metadown)
    
    (setq org-default-notes-file "~/Documents/todo.org"
          system-time-locale "C"
          org-use-speed-commands t
          org-adapt-indentation nil
          org-return-follows-link t
          org-display-remote-inline-images 'download
          org-image-actual-width (list 400)
          org-hide-emphasis-markers t
          org-outline-path-complete-in-steps nil
          org-src-tab-acts-natively t
          org-id-track-globally t
          org-confirm-babel-evaluate nil)
    
    (setq org-todo-keywords
          (quote ((sequence "TODO(t)"
                            "MIGRATE(m)" "|"
                            "IN PROGRESS(p)" 
                            "DONE(d)")
                  (sequence "WAITING(w@/!)"
                            "HOLD(h@/!)" "|"
                            "CANCELLED(c@/!)"
                            "PHONE"
                            "MEETING"))))
    (setq org-todo-keyword-faces
          (quote (("TODO" :foreground "red" :weight bold)
                  ("NEXT" :foreground "blue" :weight bold)
                  ("DONE" :foreground "forest green" :weight bold)
                  ("WAITING" :foreground "orange" :weight bold)
                  ("HOLD" :foreground "magenta" :weight bold)
                  ("CANCELLED" :foreground "forest green" :weight bold)
                  ("MEETING" :foreground "forest cyan" :weight bold)
                  ("PHONE" :foreground "blue" :weight bold))))
    
  • Org-ref (не используется)
    (load "org-ref-autoloads")
    
    (setq reftex-default-bibliography '("~/Documents/bibliography/references.bib"))
    
    ;; see org-ref for use of these variables
    (setq org-ref-bibliography-notes "~/Documents/bibliography/notes.org"
          org-ref-default-bibliography '("~/Documents/Bibliography/references.bib")
          org-ref-pdf-directory "~/Documents/bibliography/bibtex-pdfs/")
    
  • Org-roam
    (when (package-loaded? "org-roam")
      (progn
        (setq org-roam-directory (file-truename "~/projects/at-w96k/content/digarden/pages/")
              org-roam-v2-ack t
              org-roam-completion-everywhere t
              org-roam-index-file (concat org-roam-directory "/20210409054712-жизнь.org")
              org-roam-dailies-directory (concat org-roam-directory "journals/"))
    
        (org-roam-db-autosync-mode t)
    
        (defun org-roam-jump-to-index ()
          "Stub of recreating the function from V1"
          (interactive)
          (let
              ((org-roam-index org-roam-index-file))
            (find-file org-roam-index)))
    
        (define-key global-map
                    (kbd "C-c n l") 'org-roam-node-insert)
        (define-key global-map
                    (kbd "C-c n f") 'org-roam-node-find)
        (define-key global-map
                    (kbd "C-c n b") 'org-roam-buffer-toggle)
        (define-key global-map
                    (kbd "C-c n t t") 'org-roam-tag-add)
        (define-key global-map
                    (kbd "C-c n t r") 'org-roam-tag-remove)
        (define-key global-map
                    (kbd "C-c n i") 'org-roam-jump-to-index)
        (define-key global-map
                    (kbd "C-c n g") 'org-roam-graph)
        (define-key global-map
                    (kbd "C-c n d") 'org-roam-db-build-cache)
        (define-key global-map
                    (kbd "C-c n r") 'org-roam-node-random)
        (define-key global-map  
                    (kbd "C-c n j") 'org-roam-dailies-find-date)))
    
    (customize-set-variable 'org-link-descriptive t)
    
  • Агенда и Capture
    (add-to-list 'org-agenda-files
                 "~/Documents/todo.org")
    
    (setq org-directory "~/Documents"
          org-default-notes-file (concat org-directory "/todo.org"))
    

YAML

(package-loaded? "yaml-mode")

Коммуникации

Telega

(when (package-loaded? "telega")
  (setq telega-filter-custom-show-folders t
        telega-chat-fill-column 40
        telega-root-fill-column 60
        telega-url-shorten-use-images t)

  (define-key global-map (kbd "C-c t") telega-prefix-map))

Mastodon

(when (package-loaded? "mastodon")
  (setq mastodon-active-user "w96k"
        mastodon-instance-url "https://fosstodon.org/"))

Наука

Разное

Минорные твики дефолтного имакса

  • Короткие ответы на вопросы
    (if (boundp 'use-short-answers)
      (setq use-short-answers t)
    (advice-add 'yes-or-no-p :override #'y-or-n-p))
    
  • Не сохранять дубликаты в killring
  • Удалять выделенный регион при вводе текста
    (delete-selection-mode 1)
    
  • Подсвечивать текущую строку
    (global-hl-line-mode 1)
    
  • Открывать список буферов в отдельном фрейме
    (add-to-list 'special-display-buffer-names "*Buffer List*")
    (setq Buffer-menu-files-only t)
    
  • Автодополнение в echo при M-x и других командах
    (icomplete-mode 1)
    
  • Проверять орфографию
    (flyspell-mode 1)
    
  • Не спрашивать о несуществующих буферах
    (setq-default confirm-nonexistent-file-or-buffer t)
    
  • Переключение буферов
    (global-set-key (kbd "M-o")  'mode-line-other-buffer)
    
  • Минорные твики
    ;; (setq redisplay-dont-pause t)
    
    (setq select-enable-clipboard t
          select-enable-primary t)
    
    (setq completions-detailed nil)
    
    (setq kill-buffer-delete-auto-save-files t)
    (setq next-error-message-highlight t)
    
    (setq mode-line-compact 'long)
    
    (setq completions-group t)
    
    ;;(set-frame-parameter nil 'internal-border-width 0)
    
    ;; (set-window-buffer nil (current-buffer))
    
    (setq default-directory "~/"
          delete-seleciton-mode t
          inhibit-startup-message t
          initial-scratch-message nil
          custom-safe-themes t
          delete-old-versions t
          confirm-kill-processes nil
          enable-local-variables t)
    
  • Shell
    (setq ansi-color-for-comint-mode t)
    (setq shell-command-prompt-show-cwd t)
    
  • Переменная PATH в eshell
    (setq exec-path-from-shell-variables
          '("PATH" "MANPATH"))
    
    (when (and (memq window-system '(mac ns x))
               (not (eq system-type 'berkeley-unix)))
      (exec-path-from-shell-initialize))
    
  • Отображение номера колонки
    (column-number-mode)
    
  • nobreak символы
    (setq nobreak-char-display nil)
    
  • Меню
  • Сохранять временные файлы не в той же директории
    (defvar backup-dir "~/.emacs.d/backups/")
    
    (setq
     backup-by-copying t
     backup-directory-alist
     '(("~/.emacs.d/backups/"))
     version-control nil)
    
  • Календарь

    Делаем начало недели в понедельник.

    (setq calendar-week-start-day 1)
    
  • Вернуться в предыдущий буфер
    (define-key global-map (kbd "C-q C-q") 'previous-buffer)
    (define-key global-map (kbd "C-S-q C-S-q") 'next-buffer)
    
  • Смена раскладки (EN / RU) и поддержка биндов на других языках

    Работает на C-\

    (set-input-method "russian-computer")
    (toggle-input-method)
    
  • Требовать создания последней пустой строки
    (setq require-final-newline t)
    
  • Стирать текст на C-h как в Bash

    И переназначаем старые бинды

    (define-key global-map (kbd "C-h") 'delete-backward-char)
    (define-key global-map (kbd "C-c h") 'help-command)
    
  • Поддержка CamelCase в навигации
    (global-subword-mode 1)
    
  • Kills
    (define-key global-map (kbd "C-k") 'kill-region)
    
    (when (package-loaded? "whole-line-or-region")
      (whole-line-or-region-global-mode))
    
    (define-key global-map (kbd "C-w") 'backward-kill-word)
    

Браузер

(setq browse-url-browser-function #'eww-browse-url)

(add-hook 'eww-mode-hook
          (lambda ()
            (set-fill-column 80)
            (display-fill-column-indicator-mode)
            (visual-fill-column-mode)))

Tramp

(add-to-list 'tramp-remote-path 'tramp-own-remote-path)

Docker

(package-loaded? "docker")
(package-loaded? "docker-compose-mode")

Debian

Инструменты для работы с пакетным менеджером Debian'а apt'ом и смежными инструментами.

(load "debian-el-autoloads")
(load "dpkg-dev-el-autoloads")

Guix

(package-loaded? "geiser-guile")

(package-loaded? "guix")

(setq geiser-guile-binary "guile")

(with-eval-after-load 'geiser-guile
  (progn
    (add-to-list 'geiser-guile-load-path "~/projects/guix/")))

(let ((guix-copyright "~/projects/guix/etc/copyright.el"))
  (if (file-exists-p guix-copyright)
      (load-file "~/projects/guix/etc/copyright.el")))

(setq copyright-names-regexp
      (format "%s <%s>" user-full-name user-mail-address))

Nix

(package-loaded? "nix")

Direnv

(when (package-loaded? "direnv")
    (direnv-mode))

Баг-трекеры

  • Debbugs

PDF

Увеличение/уменьшение шрифта

(defun zoom-in ()
  (interactive)
  (let ((x (+ (face-attribute 'default :height)
              10)))
    (set-face-attribute 'default nil :height x)
    (set-face-attribute 'mode-line nil :height x)
    (set-face-attribute 'mode-line-inactive nil :height x)
    (set-face-attribute 'mode-line-position-face nil :height x)))

(defun zoom-out ()
  (interactive)
  (let ((x (- (face-attribute 'default :height)
              10)))
    (set-face-attribute 'default nil :height x)
    (set-face-attribute 'mode-line nil :height x)
    (set-face-attribute 'mode-line-inactive nil :height x)
    (set-face-attribute 'mode-line-position-face nil :height x)))

(define-key global-map (kbd "C-=") 'zoom-in)
(define-key global-map (kbd "C-+") 'zoom-out)

Автокомплит у yes-or-no

Полный экран

Открывать Emacs на полный экран

(add-to-list 'default-frame-alist '(fullscreen . maximized))

Фуллскрин

Отображать ровно столько строчек, сколько вмещает экран.

Не работает с native-comp.

(toggle-frame-fullscreen)

(defun fullscreen ()
"Fullscreen."
(interactive)
(x-send-client-message nil 0 nil "_NET_WM_STATE" 32
                       ;; if first parameter is '1', can't toggle fullscreen status
                       '(1 "_NET_WM_STATE_FULLSCREEN" 0)))

Удаление буфера и файла

(defun delete-file-and-buffer ()
  "Kill the current buffer and deletes the file it is visiting."
  (interactive)
  (let ((filename (buffer-file-name)))
    (if filename
        (if (y-or-n-p (concat "Do you really want to delete file " filename " ?"))
            (progn
              (delete-file filename)
              (message "Deleted file %s." filename)
              (kill-buffer)))
      (message "Not a file visiting buffer!"))))

Длинные строки

;; Better support for files with long lines
(setq-default bidi-paragraph-direction 'left-to-right)
(setq-default bidi-inhibit-bpa t)
(global-so-long-mode 1)

Make shebang (#!) file executable when saved

(add-hook 'after-save-hook #'executable-make-buffer-file-executable-if-script-p)

Enable savehist-mode for command history

(savehist-mode 1)

No rights reserved

2023-11-25 Sat 14:35