#+TITLE: Emacs configuration #+PROPERTY: header-args:emacs-lisp :tangle init.el #+STARTUP: overview * Preface GNU Emacs isn't a text editor, it's the ultimate productivity environment for hackers. This document contains the core parts of my Emacs configuration which drive the workflow I use every day. This Emacs configuration file does use =setup.el= for package management. * Default themes and faces Default theme. It is needed by =early-init.el= too. In this case we have nice themed emacs even only early-init does exist. ** Face extensions *** Common face definitions #+begin_src emacs-lisp :tangle lisp/themes/r2849-faces.el :mkdirp true ;;; -*- lexical-binding: t -*- ;;; Do not edit by hand! It is generated from readme.org. ;; ------------------------------------------------------------------------------------------------- ;; common face definitions ;; ------------------------------------------------------------------------------------------------- (defface mr:operator-face '((t (:foreground "red"))) "Face used to highlight operator characters like +, -" :group 'mr:faces) (defface mr:relop-face '((t (:foreground "dodgerblue1"))) "Face used to highlight relational operator characters like <, >=" :group 'mr:faces) (defface mr:paren-face '((t (:foreground "indianred"))) "Face used to highlight parentheses characters like (, ]" :group 'mr:faces) (defvar mr:operator-face 'mr:operator-face ) (defvar mr:relop-face 'mr:relop-face ) (defvar mr:paren-face 'mr:paren-face ) #+end_src *** Assembly mode face definitions #+begin_src emacs-lisp :tangle lisp/themes/r2849-faces.el ;; ------------------------------------------------------------------------------------------------- ;; assembly mode face definitions ;; ------------------------------------------------------------------------------------------------- (defface mr:asm-comment2-face '((t (:foreground "gray50"))) "Face used to highlight comments beginnig with ;;" :group 'mr:faces) (defface mr:asm-comment3-face '((t (:foreground "gray50"))) "Face used to highlight comments beginnig with ;;;" :group 'mr:faces) (defface mr:asm-glabel-face '((t (:foreground "sienna3"))) "Face used to highlight global labels" :group 'mr:faces) (defface mr:asm-label-face '((t (:foreground "sienna1"))) "Face used to highligh labels" :group 'mr:faces) (defface mr:asm-delim-face '((t (:foreground "firebrick"))) "Face used to highlight delimiters" :group 'mr:faces) (defface mr:asm-inst-face '((t (:foreground "DodgerBlue3"))) "Face used to highlight mnemonics" :group 'mr:faces) (defface mr:asm-reg-face '((t (:foreground "DeepSkyBlue3"))) "Face used to highlight register names" :group 'mr:faces) (defface mr:asm-dir-face '((t (:foreground "brown"))) "Face used to highlight assembler directives" :group 'mr:faces) (defface mr:asm-op-face '((t (:foreground "firebrick"))) "Face used to highlight operators" :group 'mr:faces) (defface mr:asm-op2-face '((t (:foreground "CadetBlue3"))) "Face used to highlight text operators" :group 'mr:faces) (defvar mr:asm-comment2-face 'mr:asm-comment2-face ) (defvar mr:asm-comment3-face 'mr:asm-comment3-face ) (defvar mr:asm-glabel-face 'mr:asm-glabel-face ) (defvar mr:asm-label-face 'mr:asm-label-face ) (defvar mr:asm-delim-face 'mr:asm-delim-face ) (defvar mr:asm-inst-face 'mr:asm-inst-face ) (defvar mr:asm-reg-face 'mr:asm-reg-face ) (defvar mr:asm-dir-face 'mr:asm-dir-face ) (defvar mr:asm-op-face 'mr:asm-op-face ) (defvar mr:asm-op2-face 'mr:asm-op2-face ) #+end_src *** Provide face #+begin_src emacs-lisp :tangle lisp/themes/r2849-faces.el (provide 'r2849-faces) #+end_src ** Default theme #+begin_src emacs-lisp :tangle lisp/themes/r2849-theme.el :mkdirp yes ;;; -*- lexical-binding: t -*- ;;; Do not edit by hand! It is generated from readme.org. (deftheme r2849 "Created 2023-04-13.") (custom-theme-set-variables 'r2849 '(inhibit-startup-message t) '(scroll-step 1) '(truncate-lines t) '(tool-bar-mode nil) '(scroll-bar-mode nil) '(column-number-mode t) '(size-indication-mode t) '(display-time-24hr-format t) '(display-time-day-and-date t) '(tab-always-indent nil) '(scroll-bar-mode nil) '(visible-bell nil)) (custom-set-faces '(default ((t ( :inherit nil :family "RobotoMono Nerd Font Mono" :foundry "monotype" :stipple nil :background "black" :foreground "gray70" :height 140 :weight normal )))) ;; org mode faces '(org-document-title ((t (:height 1.50 :bold t :italic nil )))) '(org-level-1 ((t (:height 1.20 :bold t :italic nil )))) '(org-level-2 ((t (:height 1.10 :bold nil :italic t )))) '(org-level-3 ((t (:height 1.05 )))) '(org-level-4 ((t (:height 1.00 )))) '(org-level-5 ((t (:height 1.00 )))) '(org-level-6 ((t (:height 1.00 )))) '(org-level-7 ((t (:height 1.00 )))) '(org-level-8 ((t (:height 1.00 )))) ;; modeline '(mode-line-emphasis ((t (:italic t :bold nil)))) ;; doom modeline '(doom-modeline-persp-name ((t (:inherit mode-line-emphasis :foreground "dark orange" :italic t)))) ;; common progmode faces '(font-lock-comment-face ((t (:foreground "gray50" )))) '(font-lock-function-name-face ((t (:foreground "deep pink" )))) '(font-lock-keyword-face ((t (:foreground "dark orange" )))) '(font-lock-string-face ((t (:foreground "dark sea green" )))) ;; current line and line number faces '(line-number-current-line ((t (:foreground "white" :italic nil :bold nil )))) '(line-number ((t (:foreground "gray50" :italic nil :bold nil )))) '(hl-line ((t (:background "gray10" )))) ) ;; Local Variables: ;; no-byte-compile: t ;; End: (provide-theme 'r2849) #+end_src * Early init Most customizations for Emacs should be put in the normal init file. However, it is sometimes necessary to have customizations take effect during Emacs startup earlier than the normal init file is processed. Such customizations can be put in the early init file. 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, such as =package-enable-at-startup=, =package-load-list=, and =package-user-dir=. #+begin_src emacs-lisp :tangle early-init.el ;;; -*- lexical-binding: t -*- ;;; Do not edit by hand! It is generated from readme.org. ;;; Disable package.el in favor of straight.el ;;; Do not allow loading from the package cache. (setq package-enable-at-startup nil) (setq package-quickstart nil) ;;; Make startup faster by reducing the frequency of garbage collection and then use a hook to ;;; measure Emacs startup time. After init, restore the default garbage collection parameters. (let ((default-gc-threshold gc-cons-threshold) (default-gc-percentage gc-cons-percentage)) (setq gc-cons-threshold most-positive-fixnum gc-cons-percentage 0.8) (add-hook 'after-init-hook (lambda () (setq gc-cons-percentage default-gc-percentage gc-cons-threshold default-gc-threshold)))) ;;; profile emacs startup (add-hook 'emacs-startup-hook (lambda () (message "*** Emacs loaded in %s seconds with %d garbage collections." (emacs-init-time "%.2f") gcs-done))) ;;; Prevent unwanted runtime builds in gccemacs (native-comp); packages are compiled ;;; ahead-of-time when they are installed and site files are compiled when gccemacs ;;; is installed. (setq comp-deferred-compilation nil) ;;; Prevent the glimpse of un-styled Emacs by disabling these UI elements early. (push '(menu-bar-lines . 0) default-frame-alist) (push '(tool-bar-lines . 0) default-frame-alist) (push '(vertical-scroll-bars) default-frame-alist) ;;; Resizing the Emacs frame can be a terribly expensive part of changing the font. By inhibiting ;;; this, we easily halve startup times with fonts that are larger than the system default. (setq frame-inhibit-implied-resize t) ;;; disable GUI elements (menu-bar-mode -1) (tool-bar-mode -1) (scroll-bar-mode -1) (setq inhibit-splash-screen t) (setq use-file-dialog nil) ;;; Default coding system (prefer-coding-system 'utf-8) (set-default-coding-systems 'utf-8) (set-terminal-coding-system 'utf-8) (set-keyboard-coding-system 'utf-8) (setq buffer-file-coding-system 'utf-8) ;;; Default frame size (add-to-list 'default-frame-alist '(height . 45)) (add-to-list 'default-frame-alist '(width . 160)) ;;; Include paths (add-to-list 'load-path (substitute-in-file-name "$XDG_CONFIG_HOME/emacs/lisp" )) (add-to-list 'load-path (substitute-in-file-name "$XDG_CONFIG_HOME/emacs/lisp/themes" )) (add-to-list 'load-path (substitute-in-file-name "$XDG_CONFIG_HOME/emacs/lisp/progmodes" )) ;;; Theme paths (add-to-list 'custom-theme-load-path (substitute-in-file-name "$XDG_CONFIG_HOME/emacs/lisp/themes")) ;;; tab-bar-mode is incompatible with perspective (as of 2023-05) so do not enable it. ;;; (setq tab-bar-format '(tab-bar-format-global)) ;;; (tab-bar-mode) (setq tab-width 4) ;;; Do not appear a bunch of transient files as untracked in the Git/Plastic repo so ;;; move them all to another location. ;;; ;;; Change the user-emacs-directory to keep unwanted things out of ~/.emacs.d (setq user-emacs-directory (expand-file-name "~/.cache/emacs/") url-history-file (expand-file-name "url/history" user-emacs-directory)) ;;; Set the cache folder for native compiled files. (when (boundp 'native-comp-eln-load-path) (startup-redirect-eln-cache (expand-file-name "eln-cache" user-emacs-directory))) ;;; Bootstrap feature =straight= to install no-littering which is needed to keep clean .emacs.d ;;; It should install itself into the redefined user-emacs-directory ! (unless (featurep 'straight) ;; Bootstrap straight.el (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage))) ;;; Use straight.el for use-package expressions (straight-use-package 'use-package) ;;; Use no-littering to automatically set common paths to the new user-emacs-directory. ;;; Load the feature =no-littering= as early as possible. Make sure it is loaded at least ;;; before any path variables are changed using some other methods. (straight-use-package 'no-littering) (require 'no-littering) ;;; Customizations must be in a separate file not in the user initialization script (init.el). (setq custom-file (locate-user-emacs-file "custom.el")) (load custom-file 'noerror 'nomessage) ;;; Load theme as soon as possible (setq custom-safe-themes t) (load-theme 'r2849) #+end_src * Streamlined configuration with setup.el Use the excellent [[https://www.emacswiki.org/emacs/SetupEl][setup.el]] by [[https://ruzkuku.com][pkal]] as an alternative to =use-package=. #+begin_src emacs-lisp (straight-use-package '(setup :type git :host nil :repo "https://git.sr.ht/~pkal/setup")) (require 'setup) ;; Uncomment this for debugging purposes ;; (defun dw/log-require (&rest args) ;; (with-current-buffer (get-buffer-create "*require-log*") ;; (insert (format "%s\n" ;; (file-name-nondirectory (car args)))))) ;; (add-to-list 'after-load-functions #'dw/log-require) #+end_src ** :straight StraightEl (on GitHub) is a “next-generation, purely functional package manager for the Emacs hacker,” according to its repo. I like using it is all I know. Here’s a :straight keyword for setup: #+begin_src emacs-lisp (setup-define :straight (lambda (recipe) `(unless (straight-use-package ',recipe) ,(setup-quit))) :documentation "Install RECIPE with `straight-use-package'. This macro can be used as HEAD, and will replace itself with the first RECIPE's package." :repeatable t :shorthand (lambda (sexp) (let ((recipe (cadr sexp))) (if (consp recipe) (car recipe) recipe)))) #+end_src ** :pkg #+begin_src emacs-lisp (defun mr/filter-straight-recipe (recipe) (let* ((plist (cdr recipe)) (name (plist-get plist :straight))) (cons (if (and name (not (equal name t))) name (car recipe)) (plist-put plist :straight nil)))) (setup-define :pkg (lambda (&rest recipe) `(straight-use-package ',(mr/filter-straight-recipe recipe))) :documentation "Install RECIPE via Guix or straight.el" :shorthand #'cadr) #+end_src ** :delay Delay the loading of a package until a certain amount of idle time has passed. #+begin_src emacs-lisp (setup-define :delay (lambda (&rest time) `(run-with-idle-timer ,(or time 1) nil ;; Don't repeat (lambda () (require ',(setup-get 'feature))))) :documentation "Delay loading the feature until a certain amount of idle time has passed.") #+end_src ** :disabled Used to disable a package configuration, similar to =:disabled= in =use-package=. #+begin_src emacs-lisp (setup-define :disabled (lambda () `,(setup-quit)) :documentation "Always stop evaluating the body.") #+end_src ** :load-after This keyword causes a body to be executed after other packages/features are loaded: #+begin_src emacs-lisp (setup-define :load-after (lambda (features &rest body) (let ((body `(progn (require ',(setup-get 'feature)) ,@body))) (dolist (feature (if (listp features) (nreverse features) (list features))) (setq body `(with-eval-after-load ',feature ,body))) body)) :documentation "Load the current feature after FEATURES." :indent 1) #+end_src ** :face Customizing faces. #+begin_src emacs-lisp (setup-define :face (lambda (face spec) `(custom-set-faces (quote (,face ,spec)))) :documentation "Customize FACE to SPEC." :signature '(face spec ...) :debug '(setup) :repeatable t :after-loaded t) #+end_src * Keyboard bindings ** ESC cancels all #+begin_src emacs-lisp (global-set-key (kbd "") 'keyboard-escape-quit) #+end_src ** Let's Be Evil Some tips can be found here: - [[https://github.com/noctuid/evil-guide][Evil guide]] - [[https://nathantypanski.com/blog/2014-08-03-a-vim-like-emacs-config.html][Vim like Emacs config]] #+begin_src emacs-lisp (setup (:straight undo-tree) (setq undo-tree-auto-save-history nil) (global-undo-tree-mode 1)) (setup (:straight goto-chg)) (setup (:straight evil) ;; Pre-load configuration (setq evil-want-integration t) (setq evil-want-keybinding nil) (setq evil-want-C-u-scroll t) (setq evil-want-C-i-jump nil) (setq evil-respect-visual-line-mode t) (setq evil-undo-system 'undo-tree) ;; Activate the Evil (evil-mode 1) ;; Set Emacs state modes (dolist (mode '(custom-mode eshell-mode git-rebase-mode erc-mode circe-server-mode circe-chat-mode circe-query-mode sauron-mode term-mode)) (add-to-list 'evil-emacs-state-modes mode)) (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state) (define-key evil-insert-state-map (kbd "C-h") 'evil-delete-backward-char-and-join) ;; Use visual line motions even outside of visual-line-mode buffers (evil-global-set-key 'motion "j" 'evil-next-visual-line) (evil-global-set-key 'motion "k" 'evil-previous-visual-line) (evil-set-initial-state 'messages-buffer-mode 'normal) (evil-set-initial-state 'dashboard-mode 'normal)) (setup (:pkg evil-collection) ;; Is this a bug in evil-collection? (setq evil-collection-company-use-tng nil) (:load-after evil (:option evil-collection-outline-bind-tab-p nil (remove evil-collection-mode-list) 'lispy (remove evil-collection-mode-list) 'org-present) (evil-collection-init))) #+end_src ** Keybinding Panel (which-key) [[https://github.com/justbur/emacs-which-key][which-key]] is great for getting an overview of what keybindings are available based on the prefix keys you entered. Learned about this one from Spacemacs. #+begin_src emacs-lisp (setup (:pkg which-key) (:load-after diminish (diminish 'which-key-mode) (which-key-mode) (setq which-key-idle-delay 0.3))) #+end_src ** Simplify leader bindings (general.el) [[https://github.com/noctuid/general.el][general.el]] is a fantastic library for defining prefixed keybindings, especially in conjunction with Evil modes. #+begin_src emacs-lisp (setup (:pkg general) (general-evil-setup t) (general-create-definer mr/leader-key :keymaps '(normal insert visual emacs) :prefix "SPC" :global-prefix "C-SPC") (general-create-definer mr/ctrl-c-keys :prefix "C-c")) #+end_src * General configuration ** User Interface Clean up Emacs' user interface, make it more minimal. Improve scrolling. #+begin_src emacs-lisp (setq mouse-wheel-scroll-amount '(3 ((shift) . 1))) ;; one line at a time (setq mouse-wheel-progressive-speed nil) ;; don't accelerate scrolling (setq mouse-wheel-follow-mouse 't) ;; scroll window under mouse (setq scroll-step 1) ;; keyboard scroll one line at a time (setq use-dialog-box nil) ;; Disable dialog boxes since they weren't working in Mac OSX #+end_src Set frame transparency (and maximize windows by default). #+begin_src emacs-lisp ; (set-frame-parameter (selected-frame) 'alpha '(90 . 90)) ; (add-to-list 'default-frame-alist '(alpha . (90 . 90))) ; (set-frame-parameter (selected-frame) 'fullscreen 'maximized) ; (add-to-list 'default-frame-alist '(fullscreen . maximized)) #+end_src Set line spacing. #+begin_src emacs-lisp (setq line-spacing 0.03) #+end_src Enable line numbers and customize their format (e.g.: relative numbers). Also set the width of the line number column. #+begin_src emacs-lisp (column-number-mode) ;; Enable line numbers for some modes (dolist (mode '(text-mode-hook prog-mode-hook conf-mode-hook)) (add-hook mode (lambda () (display-line-numbers-mode 1) (setq display-line-numbers 'relative)))) ;; Override some modes which derive from the above (dolist (mode '(org-mode-hook)) (add-hook mode (lambda () (display-line-numbers-mode 1) (setq display-line-numbers 'relative)))) (setq-default display-line-numbers-width 4) #+end_src Don't warn for large files (shows up when launching videos) Don't warn for following symlinked files Don't warn when advice is added for functions #+begin_src emacs-lisp (setq large-file-warning-threshold nil) (setq vc-follow-symlinks t) (setq ad-redefinition-action 'accept) #+end_src ** Theme These days I bounce around between themes included with [[https://github.com/hlissner/emacs-doom-themes][DOOM Themes]] since they're well-designed and integrate with a lot of Emacs packages. A nice gallery of Emacs themes can be found at https://emacsthemes.com/. Alternate themes: - =doom-snazzy= - =doom-vibrant= #+begin_src emacs-lisp (setup (:pkg spacegray-theme)) (setup (:pkg doom-themes)) (add-hook 'emacs-startup-hook (lambda () ; (load-theme 'doom-palenight t) (doom-themes-visual-bell-config))) #+end_src ** Mode Line *** Basic Customization #+begin_src emacs-lisp (setq display-time-format "%l:%M %p %b %y" display-time-default-load-average nil) #+end_src *** Enable Mode Diminishing The [[https://github.com/myrjola/diminish.el][diminish]] package hides pesky minor modes from the modelines. #+begin_src emacs-lisp (setup (:pkg diminish)) #+end_src *** Doom Modeline #+begin_src emacs-lisp ;; You must run (nerd-icons-install-fonts) one time after ;; installing this package! (setup (:straight minions) (:hook-into doom-modeline-mode)) (setup (:pkg shrink-path)) (setup (:pkg doom-modeline) (:hook-into after-init-hook) (:option doom-modeline-height 21 doom-modeline-bar-width 6 doom-modeline-lsp t doom-modeline-github nil doom-modeline-mu4e nil doom-modeline-irc t doom-modeline-modal-icon nil doom-modeline-minor-modes t doom-modeline-persp-name nil doom-modeline-buffer-file-name-style 'truncate-except-project doom-modeline-major-mode-icon nil) (:face mode-line ((t (:height 0.95)))) (:face mode-line-inactive ((t (:height 0.95))))) ; (setq doom-modeline-display-default-persp-name nil) ; (setq doom-modeline-persp-name nil) #+end_src