Table of Contents

Getting Started

This is my personal Emacs configuration. It's deployed on my personal site via the Emacs Org publish system that generates Org files to HTML. In other words, it's a static site generator that takes Org file input and outputs a static website.

Try pulling from the main file in the blog repo. In Emacs, navigate to this file and call M-x org-babel-tangle, which bounds to keychord C-c C-v C-t as a shortcut. This will create or override the init.el file that Emacs reads to tweak its config.

Setup

Emacs documents all of its variables. When in doubt, call M-x describe-variable or M-x describe-function. Keychords may also be used: C-h v [variable symbol], C-h f [function symbol] respectively.

Package manager

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))

Default Emacs UI

(menu-bar-mode -1)            ; Disable for more headspace
(tool-bar-mode -1)                      ; Remove toolbar
(scroll-bar-mode -1)                    ; Remove scroll bar
(save-place-mode 1)           ; Remember cursor position
(load-theme 'modus-vivendi nil)

General

(setq-default inhibit-startup-message t              ; Don't show default emacs startup screen
              window-combination-resize t            ; Resize windows proportionally
              display-time-default-load-average nil  ; Don't show system load time in modeline
              indent-tabs-mode nil                   ; Stop using tabs to indent
              tab-width 4                                            ; Change default tab width

              ;; My machine has enough memory to increase the default values
              gc-cons-threshold 50000000
              large-file-warning-threshold 100000000

              ;; Increase default max recursion depth (when coding in Elisp)
              max-specpdl-size 19500
              max-lisp-eval-depth 24000

              bidi-paragraph-direction 'left-to-right ; Just work with left to right langs
              bidi-inhibit-bpa t

              ;; Directories
              delete-by-moving-to-trash t
              auto-revert-check-vc-info t

              use-short-answers t)

;; Toggle features not available in a fresh Emacs install
(put 'downcase-region 'disabled nil)                 ; Allow use of C-x C-l (downcase region)
(put 'upcase-region 'disabled nil)                   ; Allow use of C-x C-u (capitalize region)
(put 'narrow-to-region 'disabled nil)                ; Allows to narrow region

;; Useful Register setup when appending/prepending
(setq register-separator ?+)
(set-register register-separator "\n\n")
;; (set-register ?z '(file . ""))

;; Useful functionality
(add-hook 'before-save-hook 'delete-trailing-whitespace)
(add-hook 'after-save-hook
          'executable-make-buffer-file-executable-if-script-p)

(global-set-key (kbd "C-x k") 'kill-this-buffer)

(global-so-long-mode 1)

;; Change Emacs backup files location
(setq backup-directory-alist
      '(("." . "~/.config/emacs")))

;; This change prevents the crashing of the React server after any file change
(setq create-lockfiles nil)

;; inherit PATH from shell, depends on `exec-path-from-shell' package
(exec-path-from-shell-copy-envs '("LANG" "LC_TYPE" "SSH_AUTH_SOCK" "SSH_AGENT_PID"))
(exec-path-from-shell-initialize)
(setq project-vc-extra-root-markers '("tsconfig.json" "build.clj"))
(setq project-vc-ignores '("node_modules"))

Custom prefix keys

Some functions get called often but are not binded to a keychord by default. Custom prefix keys allows defining a key and bind it to a function.

;; Keys may be binded globally like below
(global-set-key (kbd "<C-M-return>") 'delete-other-windows)

;; Define C-z key map
(define-prefix-command 'my-super-z-map)
;; Bind C-z to custom prefix command
(global-set-key (kbd "C-z") 'my-super-z-map)

;; Set of mapped keys in `C-z'
(define-key my-super-z-map (kbd "o") 'browse-url-at-point)
(define-key my-super-z-map (kbd "a") 'add-file-local-variable-prop-line)

Org

(with-eval-after-load 'org
  (visual-line-mode 1) ; wrap lines
  (setq org-src-fontify-natively t    ; highlight syntax in code source blocks
        org-catch-invisible-edits t))

;; Org Babel Setup
(org-babel-do-load-languages
 'org-babel-load-languages
 '((shell . t)
   (latex . t)
   (python . t)
   (ditaa . t)))

Org Tree Slide

Create slide-shows with Org mode.

(with-eval-after-load 'org-tree-slide-mode
  (org-image-actual-width nil))

Extras

Nice to have packages and functionality. Most of these improve coding experience.

Electricity

;; Electric Layout Mode
(add-hook 'css-mode 'electric-layout-mode)    ; insert newline after the insertion of '{'

(electric-indent-mode +1) ; toggle on the fly re-indentation

;; Electric Pairs
(add-hook 'mhtml-mode-hook 'electric-pair-local-mode)
(add-hook 'emacs-lisp-mode-hook 'electric-pair-local-mode)
(add-hook 'clojure-mode-hook 'electric-pair-local-mode)
(add-hook 'lisp-interaction-mode-hook 'electric-pair-local-mode)
(add-hook 'web-mode-hook 'electric-pair-local-mode)
(add-hook 'ielm-mode-hook 'electric-pair-local-mode)
(add-hook 'js-mode-hook 'electric-pair-local-mode)
(add-hook 'typescript-mode-hook 'electric-pair-local-mode)
(add-hook 'org-mode-hook 'electric-pair-local-mode)
(add-hook 'scheme-mode-hook 'electric-pair-local-mode)
(add-hook 'python-mode-hook 'electric-pair-local-mode)
(add-hook 'css-mode-hook 'electric-pair-local-mode)

;; Add extra pairs for js-mode
(defvar js-mode-electric-pairs '((?` . ?`)) "Electric pairs for js-mode.")
(defun js-mode-add-electric-pairs ()
  (setq-local electric-pair-pairs (append electric-pair-pairs js-mode-electric-pairs))
  (setq-local electric-pair-text-pairs electric-pair-pairs))
(add-hook 'js-mode-hook 'js-mode-add-electric-pairs)
(add-hook 'mhtml-mode-hook 'js-mode-add-electric-pairs) ; needs it for `script` tags

;; Subword Mode
(add-hook 'js-mode-hook #'subword-mode)
(add-hook 'js-jsx-mode-hook #'subword-mode)
(add-hook 'typescript-mode-hook #'subword-mode)
(add-hook 'python-mode-hook #'subword-mode)
(add-hook 'c-mode-hook #'subword-mode)
(add-hook 'clojure-mode-hook #'subword-mode)

;; Enable Dash font-locking
(eval-after-load 'dash '(dash-enable-font-lock))

Prettify Symbols

(global-prettify-symbols-mode t)

(defun my-add-pretty-lambda ()
  "Make some word or string show as pretty Unicode symbols"
  (push '("lambda" . 955) prettify-symbols-alist)             ; λ
  (push '("->" . 8594) prettify-symbols-alist)            ; 
  (push '("=>" . 8658) prettify-symbols-alist)            ; 
  (push '("map" . 8614) prettify-symbols-alist)               ; 
  )

(add-hook 'tex-mode-hook 'my-add-pretty-lambda)

(add-hook 'emacs-lisp-mode-hook
          (lambda ()
            "Beautify Emacs Symbols"
            (push '("<=" . "≤") prettify-symbols-alist)))

(add-hook 'scheme-mode-hook
          (lambda ()
            "Beautify Emacs Symbols"
            (push '("<=" . "≤") prettify-symbols-alist)))

(add-hook 'clojure-mode-hook
          (lambda ()
            "Beautify Clojure Symbols"
            (push '("map" . 8614) prettify-symbols-alist)
            (push '("->" . 8594) prettify-symbols-alist)))

Rainbow Delimiters

(custom-set-faces
       '(rainbow-delimiters-depth-1-face ((t (:foreground "blue violet"))))
       '(rainbow-delimiters-depth-2-face ((t (:foreground "red"))))
       '(rainbow-delimiters-depth-3-face ((t (:foreground "cyan3"))))
       '(rainbow-delimiters-depth-4-face ((t (:foreground "blue"))))
       '(rainbow-delimiters-depth-5-face ((t (:foreground "gold"))))
       '(rainbow-delimiters-depth-6-face ((t (:foreground "lavender"))))
       '(rainbow-delimiters-depth-7-face ((t (:foreground "ivory"))))
       '(rainbow-delimiters-depth-8-face ((t (:foreground "magenta"))))
       '(rainbow-delimiters-depth-9-face ((t (:foreground "red")))))

(add-hook 'clojure-mode-hook #'rainbow-delimiters-mode)
(add-hook 'emacs-lisp-mode-hook #'rainbow-delimiters-mode)
(add-hook 'ielm-mode-hook #'rainbow-delimiters-mode)
(add-hook 'lisp-interaction-mode-hook #'rainbow-delimiters-mode)
(add-hook 'lisp-mode-hook #'rainbow-delimiters-mode)

Packages

Vertico / Orderless / CtrlF / Marginalia / Consult

Better buffer search and improved minibuffer experience

    (vertico-mode)
    (marginalia-mode)

    (setq completion-styles '(orderless basic)
          completion-category-overrides '((file (styles basic partial-completion))))

    (advice-add #'vertico--setup :after
                (lambda (&rest _)
                  (setq-local completion-auto-help nil
                              completion-show-inline-help nil)))

    ;; Consult
    (global-set-key (kbd "C-x b") 'consult-buffer)
    (global-set-key (kbd "C-x 4 b") 'consult-buffer-other-window)
    (global-set-key (kbd "M-s G") 'consult-git-grep)
    (global-set-key (kbd "M-g g") 'consult-goto-line)
    (global-set-key (kbd "M-g M-g") 'consult-goto-line)
    (global-set-key (kbd "M-g f") 'consult-flymake)
    (global-set-key (kbd "C-x p b") 'consult-project-buffer)
    (global-set-key (kbd "M-s l") 'consult-line)

    ;; Use Consult to select xref locations with preview
    (setq xref-show-xrefs-function #'consult-xref
          xref-show-definitions-function #'consult-xref)

    ;; Enable vertico-multiform
(vertico-multiform-mode)

;; Configure the display per completion category.
;; Use the grid display for files and a buffer
;; for the consult-grep commands.
(setq vertico-multiform-categories
      '(
        ;;(file grid)
        (consult-grep buffer)))

Corfu & Cape

Auto-completion in Emacs.

;; TAB cycle if there are only few candidates
(setq completion-cycle-threshold 3)
(setq tab-always-indent 'complete)

(setq corfu-auto t)
(global-corfu-mode)

;; Cape
;; Add `completion-at-point-functions', used by `completion-at-point'.
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-keyword)
(add-to-list 'completion-at-point-functions #'cape-sgml)
(add-to-list 'completion-at-point-functions #'cape-symbol)

Dired

(setq dired-recursive-copies 'always ; “always” means no asking
      dired-recursive-deletes 'top ; “top” means ask once
      dired-dwim-target t)

(add-hook 'dired-mode-hook '(lambda ()
                              (dired-hide-details-mode 1)))

(put 'dired-find-alternate-file 'disabled nil)

Web Development

(setq css-indent-offset 2)

(add-hook 'js-mode-hook
          (lambda ()
            (define-key js-mode-map (kbd "M-.")
              'xref-find-definitions)))

;; Configure Eglot Eslint/Flymake with JSX and TSX
;; wraps `flymake-eslint-enable' to run only root dirs with `.eslintrc' file
(defun me/flymake-eslint-enable-maybe ()
  "Enable `flymake-eslint' based on the project configuration.
             Search for the project ESLint configuration to determine whether the buffer
             should be checked."
  (when-let* ((root (locate-dominating-file (buffer-file-name) "package.json"))
              (rc (locate-file ".eslintrc" (list root) '(".js" ".json"))))
    (make-local-variable 'exec-path)
    (push (file-name-concat root "node_modules" ".bin") exec-path)
    (flymake-eslint-enable)))


(add-hook 'eglot-managed-mode-hook (lambda ()
                                     (me/flymake-eslint-enable-maybe)))

(add-hook 'typescript-mode-hook (lambda ()
                          (setq-local eglot-stay-out-of '(flymake))
                          (eglot-ensure)))
(add-hook 'js-mode-hook (lambda ()
                          (setq-local eglot-stay-out-of '(flymake))
                          (eglot-ensure)))
(add-hook 'js-jsx-mode-hook (lambda ()
                              (setq-local eglot-stay-out-of '(flymake))
                              (eglot-ensure)))

Clojure

(setq cider-clojure-cli-aliases "-M:dev"
      cider-eval-result-prefix "=>"
      cider-repl-display-help-banner nil)

(remove-hook 'flymake-diagnostic-functions #'flymake-proc-legacy-flymake)
(add-hook 'clojure-mode-hook #'flymake-kondor-setup)
(add-hook 'clojure-mode-hook (lambda ()
                               (add-hook 'after-save-hook #'eglot-format-buffer nil 'make-it-local)))
(add-hook 'clojure-mode-hook 'eglot-ensure)

Dev tools

;; Tree-sitter

 ;; activate tree-sitter on any buffer containing code for which it has a parser available
  (global-tree-sitter-mode)
  ;; you can easily see the difference tree-sitter-hl-mode makes for python, ts or tsx
  ;; by switching on and off
  (add-hook 'tree-sitter-after-on-hook #'tree-sitter-hl-mode)

 ;; we choose this instead of tsx-mode so that eglot can automatically figure out language for server
  ;; see https://github.com/joaotavora/eglot/issues/624 and https://github.com/joaotavora/eglot#handling-quirky-servers
  (define-derived-mode typescriptreact-mode typescript-mode
    "TypeScript TSX")

  ;; use our derived mode for tsx files
  (add-to-list 'auto-mode-alist '("\\.tsx?\\'" . typescriptreact-mode))
  ;; by default, typescript-mode is mapped to the treesitter typescript parser
  ;; use our derived mode to map both .tsx AND .ts -> typescriptreact-mode -> treesitter tsx
  (add-to-list 'tree-sitter-major-mode-language-alist '(typescriptreact-mode . tsx))

Emacs 27.1 (Org mode 9.3)