This is (will be) my Emacs literate configuration file. A self contained file with all my configuration is useful for documentation purposes. It will be modeled using the technique described by Protesilaos for his own Emacs config file: . This method consists in generating all files /a priori/, after modifying this file, and *not* at load time, as that would be too slow. #+begin_src emacs-lisp :tangle no :results none (org-babel-tangle) #+end_src * Overview of files and directories - =early-init.el=: quoting the [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Early-Init-File.html][Emacs documentation]], this file is "loaded before the package system and GUI is initialized, so in it you can customize variables that affect the package initialization process" - =init.el=: the skeleton of my configuration framework. It will load the rest of the modules. - =rul-emacs-modules/=: a directory with Emacs modules specific to my configuration. Modules group code related to a topic or theme of configuration. For example, =rul-prog.el= contains code related to programming, and =rul-org.el= contains code related to org-mode. If a module gets too big, I can create a smaller module under the same topic; for example, =rul-org-agenda.el=. - =rul-post-init.el=: this file will be loaded after =init.el=, and will normally live in other git repository. Here I normally add overrides needed in my work computer. - =rul-emacs.org=: this file. It (will) generate the rest of the structure. * Early configuration file (=early-init.el=) ** Graphical aspects Customization of graphical aspects of Emacs, such as size, panels, etc. #+begin_src emacs-lisp :tangle "early-init.el" ;; I don't use any of these (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) ;; Avoid initial flash of light. ;; Inspired on prot-emacs-avoid-initial-flash-of-light. (setq mode-line-format nil) (set-face-attribute 'default nil :background "#000000" :foreground "#ffffff") (set-face-attribute 'mode-line nil :background "#000000" :foreground "#ffffff" :box 'unspecified) #+end_src ** Frame configuration I like to keep a few frames open all the time. A main frame, where I open my org files, code, etc. A frame for communication and reading, such as email and feeds, and a frame for terminals. Currently, the frames are all the same, but I will add configuration to distinguish them so I can automate their placement in my desktop environment. #+begin_src emacs-lisp :tangle "early-init.el" ;; Do not resize when font size changes (setq frame-resize-pixelwise t) ;; By default, start maximized (add-to-list 'default-frame-alist '(fullscreen . maximized)) ;; No need for titlebar (modify-frame-parameters nil '((undecorated . t))) ;; Name frames to ease switching between them (add-hook 'after-init-hook (lambda () (set-frame-name "main"))) #+end_src ** Miscellany #+begin_src emacs-lisp :tangle "early-init.el" ;; Initialise installed packages, otherwise, basic functions are not ;; available during the initialization stage. (setq package-enable-at-startup t) ;; Do not report warnings. It's too noisy. (setq native-comp-async-report-warnings-errors 'silent) ;; Keep things minimal (setq inhibit-startup-screen t) (setq inhibit-startup-echo-area-message user-login-name) #+end_src * Main configuration file (=init.el=) ** Package matters I use package from both stable and bleeding-edge Melpa. #+begin_src emacs-lisp :tangle "init.el" (add-to-list 'package-archives '("melpa-stable" . "https://stable.melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) #+end_src ** Backups Emacs tends to clutter the filesystem with backup files. A backup file is normally the filename with a =~= suffix. I rather have my filesystem clean, and centralize all backups in a single directory. #+begin_src emacs-lisp :tangle "init.el" (if (file-directory-p "~/.backup") (setq backup-directory-alist '(("." . "~/.backup"))) (message "Directory does not exist: ~/.backup")) (setq backup-by-copying t ; Don't delink hardlinks delete-old-versions t ; Clean up the backups kept-new-versions 3 ; keep some new versions kept-old-versions 2 ; and some old ones, too version-control t) ; Use version numbers on backups #+end_src ** Customizations Customizations don't place nicely with version control, so I do them in a random file that won't get persisted. Configurations that need persisting will be added to =custom-set-variables= and =custom-set-faces=. #+begin_src emacs-lisp :tangle "init.el" ;; Do not persist customizations (setq custom-file (make-temp-file "emacs-custom-")) #+end_src ** Editor interface General configurations related to text editing across all modes. #+begin_src emacs-lisp :tangle "init.el" (setq fill-column 79) ; Wrap lines (setq mouse-yank-at-point t) ; Do not follow mouse curors when mouse-yanking (setq-default indent-tabs-mode nil) ; No tabs when indenting (setq-default tab-width 4) ; How many spaces a tab represents (setq initial-scratch-message "") (defalias 'yes-or-no-p 'y-or-n-p) ;; Only flash the mode line (setq ring-bell-function (lambda () (let ((orig-fg (face-foreground 'mode-line))) (set-face-foreground 'mode-line "#F2804F") (run-with-idle-timer 0.1 nil (lambda (fg) (set-face-foreground 'mode-line fg)) orig-fg)))) ;; Highlight parens (setq show-paren-delay 0) (show-paren-mode 1) (savehist-mode 1) ; Save histories, including minibuffer (save-place-mode 1) ; Remember and restore cursor information (setq auto-save-no-message t) ; Do not print a message when auto-saving (pixel-scroll-precision-mode 1) ; Precision scrolling #+end_src ** Emacs server I used to run Emacs as a systemd daemon, but it was not too deterministic as sometimes it would break. https://rbenencia.name/blog/emacs-daemon-as-a-systemd-service/ Now, I simply start it from Emacs itself. This approach works well for me. #+begin_src emacs-lisp :tangle "init.el" ;; Server (require 'server) (setq server-client-instructions nil) ; Keep it quiet when opening an ec (unless (server-running-p) (server-start)) #+end_src ** Modules machinery #+begin_src emacs-lisp :tangle "init.el" (dolist (path '("~/.emacs.d/rul-lisp/packages")) (add-to-list 'load-path path)) (when-let* ((file (locate-user-emacs-file "rul-pre-init.el")) ((file-exists-p file))) (load-file file)) (require 'rul-themes) (require 'rul-bindings) (require 'rul-completion) (require 'rul-feeds) (require 'rul-fm) (require 'rul-fonts) (require 'rul-mail) (require 'rul-media) (require 'rul-modeline) (require 'rul-org) (require 'rul-prog) (require 'rul-terminals) (require 'rul-vc) (require 'rul-wm) (require 'rul-write) (when-let* ((file (locate-user-emacs-file "rul-post-init.el")) ((file-exists-p file))) (load-file file)) ;; init.el ends here #+end_src * Modules I group my configuration in logical modules. In general, a module contains configuration for more than one package. ** The =themes= module The =themes= module contains code pertaining to Emacs themes. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-themes.el" (use-package ef-themes :ensure t) (use-package modus-themes :ensure t :config (setq modus-themes-mode-line '(accented borderless padded) modus-themes-region '(bg-only) modus-themes-bold-constructs t modus-themes-italic-constructs t modus-themes-paren-match '(bold intense) modus-themes-headings (quote ((1 . (rainbow variable-pitch 1.3)) (2 . (rainbow 1.1)) (t . (rainbow)))) modus-themes-org-blocks 'tinted)) #+end_src Additionally, this module subscribes to =org.freedesktop.appearance color-theme= to detect what color theme is preferred, and set our Emacs theme accordingly. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-themes.el" (use-package dbus) (defun mf/set-theme-from-dbus-value (value) "Set the appropiate theme according to the color-scheme setting value." (message "value is %s" value) (if (equal value '1) (progn (message "Switch to dark theme") (modus-themes-select 'modus-vivendi)) (progn (message "Switch to light theme") (modus-themes-select 'modus-operandi)))) (defun mf/color-scheme-changed (path var value) "DBus handler to detect when the color-scheme has changed." (when (and (string-equal path "org.freedesktop.appearance") (string-equal var "color-scheme")) (mf/set-theme-from-dbus-value (car value)) )) ;; Register for future changes (dbus-register-signal :session "org.freedesktop.portal.Desktop" "/org/freedesktop/portal/desktop" "org.freedesktop.portal.Settings" "SettingChanged" #'mf/color-scheme-changed) ;; Request the current color-scheme (dbus-call-method-asynchronously :session "org.freedesktop.portal.Desktop" "/org/freedesktop/portal/desktop" "org.freedesktop.portal.Settings" "Read" (lambda (value) (mf/set-theme-from-dbus-value (caar value))) "org.freedesktop.appearance" "color-scheme" ) (provide 'rul-themes) #+end_src ** The =bindings= module This module contains code pertaining to keybindings. It starts by defining a set global keys. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el" ;; Global keybindings (global-set-key (kbd "C-c R") 'revert-buffer) (global-set-key (kbd "C-c w") 'whitespace-cleanup) (defun help/insert-em-dash () "Inserts an EM-DASH (not a HYPEN, not an N-DASH)" (interactive) (insert "—")) (global-set-key (kbd "C--") #'help/insert-em-dash) #+end_src Next, we define a few /hydras/. /Hydras/ are a way of grouping keybindings together, offering a menu on the way. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el" (use-package hydra :ensure t :defer 1) ;; tab-bar (defhydra hydra-tab-bar (:color amaranth) "Tab Bar Operations" ("t" tab-new "Create a new tab" :column "Creation" :exit t) ("d" dired-other-tab "Open Dired in another tab") ("f" find-file-other-tab "Find file in another tab") ("x" tab-close "Close current tab") ("m" tab-move "Move current tab" :column "Management") ("r" tab-rename "Rename Tab") ("" tab-bar-select-tab-by-name "Select tab by name" :column "Navigation") ("l" tab-next "Next Tab") ("j" tab-previous "Previous Tab") ("q" nil "Exit" :exit t)) (global-set-key (kbd "C-x t") 'hydra-tab-bar/body) ;; Zoom (defhydra hydra-zoom () "zoom" ("g" text-scale-increase "in") ("l" text-scale-decrease "out")) (global-set-key (kbd "C-c z") 'hydra-zoom/body) ;; Go (defhydra hydra-go () "zoom" ("=" gofmt :exit t) ("c" go-coverage :exit t)) ;; vterm (defhydra hydra-vterm () "zoom" ("t" multi-vterm "Open a terminal" :exit t) ("d" multi-vterm-dedicated-open "Dedicated" :exit t) ("p" multi-vterm-prev "Previous terminal") ("n" multi-vterm-next "Next terminal") ("r" multi-vterm-rename-buffer "Rename buffer" :exit t) ) (global-set-key (kbd "C-c t") 'hydra-vterm/body) (global-set-key (kbd "C-c m") 'hydra-go/body) #+end_src Finally, we make use of =which-key=, which will show a menu with all keybinding options after a prefix is pressed. I think this package has the potential to obsolete =hydra=, so I'll have to revisit that code. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-bindings.el" (use-package which-key :ensure t :config (which-key-mode)) (provide 'rul-bindings) #+end_src ** The =completions= module This module contains code pertaining to completion and the minibuffer. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" (use-package orderless :ensure t) (setq completion-styles '(basic substring initials orderless)) (setq completion-category-overrides '( (file (styles . (basic partial-completion orderless))) (project-file (styles . (flex basic substring partial-completion orderless))) )) (setq completion-ignore-case t) #+end_src The =vertico= package provides a vertical completion UI based on the default completion system. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" ;; Enable vertico (use-package vertico :ensure t :init (vertico-mode) :config (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)) #+end_src The =marginalia= package annotates the completion candidates with useful contextual information. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" ;; Enable rich annotations using the Marginalia package (use-package marginalia :ensure t :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode)) #+end_src The =consult= package replaces most of Emacs core functions with completion-friendly alternatives that integrates well with =vertico= and =marginalia=. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" (use-package consult :ensure t :bind (;; C-c bindings in `mode-specific-map' ("C-c M-x" . consult-mode-command) ("C-c h" . consult-history) ("C-c k" . consult-kmacro) ("C-c m" . consult-man) ("C-c i" . consult-info) ([remap Info-search] . consult-info) ;; C-x bindings in `ctl-x-map' ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command ("C-x b" . consult-buffer) ;; orig. switch-to-buffer ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer ;; Custom M-# bindings for fast register access ("M-#" . consult-register-load) ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) ("C-M-#" . consult-register) ;; Other custom bindings ("M-y" . consult-yank-pop) ;; orig. yank-pop ;; M-g bindings in `goto-map' ("M-g e" . consult-compile-error) ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck ("M-g g" . consult-goto-line) ;; orig. goto-line ("M-g M-g" . consult-goto-line) ;; orig. goto-line ("M-g o" . consult-outline) ;; Alternative: consult-org-heading ("M-g m" . consult-mark) ("M-g k" . consult-global-mark) ("M-g i" . consult-imenu) ("M-g I" . consult-imenu-multi) ;; M-s bindings in `search-map' ("M-s d" . consult-find) ("M-s D" . consult-locate) ("M-s g" . consult-grep) ("M-s G" . consult-git-grep) ("M-s r" . consult-ripgrep) ("M-s l" . consult-line) ("M-s L" . consult-line-multi) ("M-s k" . consult-keep-lines) ("M-s u" . consult-focus-lines) ;; Isearch integration ("M-s e" . consult-isearch-history) :map isearch-mode-map ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string ("M-s l" . consult-line) ;; needed by consult-line to detect isearch ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch ;; Minibuffer history :map minibuffer-local-map ("M-s" . consult-history) ;; orig. next-matching-history-element ("M-r" . consult-history)) ;; orig. previous-matching-history-element :init (setq xref-show-xrefs-function #'consult-xref) (setq xref-show-definitions-function #'consult-xref) (add-hook 'completion-list-mode-hook #'consult-preview-at-point-mode) :config (setq consult-preview-key 'any) (setq consult-narrow-key "<") ) #+end_src The next piece of code corresponds to =embark=, a package that enables context-specific actions in the minibuffer, or common buffers. #+begin_src emacs-lisp :tangle "rul-lisp/packages/rul-completion.el" (use-package embark :ensure t :bind (("C-." . embark-act) ;; pick some comfortable binding ("M-." . embark-dwim) ;; good alternative: M-. ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' :init (setq prefix-help-command #'embark-prefix-help-command) :config ;; Hide the mode line of the Embark live/completions buffers (add-to-list 'display-buffer-alist '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" nil (window-parameters (mode-line-format . none))))) (use-package embark-consult :ensure t :hook (embark-collect-mode . consult-preview-at-point-mode)) (provide 'rul-completion) #+end_src