Skip to content

This is my emacs, there are many like it, but this one is mine...

Notifications You must be signed in to change notification settings

trevorbernard/emacs.d

Repository files navigation

Emacs Configuration

Configuration

This is my emacs, there are many like it, but this one is mine…

Getting Started

I currently use 29.x on both Linux and Mac systems. YMMV on Windows or different versions of Emacs.

You will need the following already installed for this configuration to run correctly.

Install the Fira Code Retina font because it’s beautiful and makes for a wonderful font. You will need aspell installed in order to use Flymake mode. Clojure mode requires leiningen.

I strongly suggest you remap your Caps Lock key to Control to help reduce the onset of Emacs pinky. I even go further and have a new key binding for M-x, so you are hitting Ctrl instead of Alt.

Preamble

Use lexical bindings and set the gc-cons-threshold to something usable for the startup process.

;;; -*- lexical-binding: t -*-
(setq gc-cons-threshold (* 1024 1024 100))

General use-package settings

Also, include melpa and melpa-stable repositories. Ensure that use-package is installed and configured.

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

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(eval-when-compile
  (setq use-package-always-defer t
        use-package-verbose nil  ; Set to t for debugging, nil for performance
        use-package-minimum-reported-time 0.1)
  (require 'use-package))

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

Emacs Initialization

I like to have my Emacs clean, crisp, and minimal. Disable the menu bar, tool bar, and scroll bar. Protip: Learn the Emacs navigation key strokes until they are second nature. You can thank me later.

(use-package emacs
  :config
  (menu-bar-mode -1)
  (tool-bar-mode -1)
  (scroll-bar-mode -1)
  (set-fringe-mode 10))

Package Settings

To improve startup performance, we’ll manually set up the load-path and avoid calling (package-initialize). This custom setup consists of several key settings:

Setting load-prefer-newer to t, ensuring newer versions of elisp files are preferred Defining package-user-dir explicitly as “~/.emacs.d/elpa” Setting package--init-file-ensured to t, which prevents package.el from automatically initializing Setting package-enable-at-startup to nil to prevent automatic package loading at startup

We also create the package directory if it doesn’t exist yet, then add all package directories to the load path. With this configuration, use-package will handle package loading instead of the default mechanisms.

(eval-when-compile
  (setq load-prefer-newer t
        package-user-dir "~/.emacs.d/elpa"
        package--init-file-ensured t
        package-enable-at-startup nil)

  (unless (file-directory-p package-user-dir)
    (make-directory package-user-dir t))

  (dolist (dir (directory-files package-user-dir t "^[^.]" t))
    (when (file-directory-p dir)
      (add-to-list 'load-path dir))))

Theme

(use-package timu-spacegrey-theme
  :ensure t
  :hook (after-init . (lambda () (load-theme 'timu-spacegrey t))))

(use-package mood-line
  :ensure t
  :hook (after-init . mood-line-mode)
  :custom
  (mood-line-glyph-alist mood-line-glyphs-fira-code))

Rainbow Delimiters

(use-package rainbow-delimiters
  :ensure t
  :defer t
  :hook ((prog-mode . rainbow-delimiters-mode)))

Personal

That’s me.

(setq user-full-name "Trevor Bernard"
      user-mail-address "[email protected]")

Key Bindings

Ignore minimize functionality when you’re in the GUI because it’s very annoying to accidentally minimize your window.

(add-hook 'emacs-startup-hook
          (lambda ()
            (when window-system
              (keymap-global-set "C-z" 'ignore)
              (keymap-global-set "C-x C-z" 'ignore))))

Invoke M-x without the Alt key

M-x is one of the most widely used key combinations in Emacs but it’s also the most annoying. You have to scrunch your left thumb and forefinger in the most uncomfortable RSI-inducing way.

I choose to rebind M-x to C-x C-m because of an article Steve Yegge wrote called: Effective Emacs. This allows you to keep your fingers on the home row if you have Caps Lock mapped to Control. With some practice, it will become second-nature.

(add-hook 'emacs-startup-hook
          (lambda ()
            (keymap-global-set "C-x C-m" 'execute-extended-command)
            (keymap-global-set "C-c C-m" 'execute-extended-command)))

Preferences

(setq
 ;; Don't display the emacs apropos
 inhibit-startup-message t
 ;; Allow short answers 'y' or 'n'
 use-short-answers t
 ;; Make pgup/dn remember current line
 scroll-preserve-screen-position t)

;; Auto revert buffers
(global-auto-revert-mode t)
;; Show column number
(column-number-mode 1)
;; Allow delete of selection
(delete-selection-mode 1)
;; Syntax Highlighting
(global-font-lock-mode 1)
;; Highlight parenthesis
(show-paren-mode 1)
;; Highlight selected Regions
(transient-mark-mode 1)

Tidy Up: Disabling Unnecessary File Artifacts

By default, Emacs generates backup files, auto-save files, and lockfiles. While once essential for crash recovery, these artifacts are often redundant today, especially with modern system stability and version control. Instead of cluttering your workspace, let’s turn them off:

(setq
 make-backup-files nil    ; No backup~ files
 auto-save-default nil    ; No #autosave# files
 create-lockfiles nil)    ; No .#lock files

Use spaces in favour of tabs because they are evil. But when there are tabs show them as 8 spaces.

(setq-default indent-tabs-mode nil)
(setq-default c-basic-offset 4)
(setq-default tab-width 8)

Limit the default fill mode to 80 characters

(setq-default fill-column 80)
(setq-default truncate-lines nil)

Ignore the stupid ring bell feature.

(setq ring-bell-function 'ignore)

Allow functions without issuing warnings

(put 'downcase-region 'disabled nil)
(put 'narrow-to-region 'disabled nil)
(put 'upcase-region 'disabled nil)

Mac specific configuration

Load environment variables from shell and set Mac-specific options.

(when (eq system-type 'darwin)
 (use-package exec-path-from-shell
   :ensure t
   :config
   (exec-path-from-shell-initialize))

 ;; Mac file handling - move files to dedicated Emacs trash
 (setq delete-by-moving-to-trash t)
 (setq trash-directory "~/.Trash/emacs")

 ;; Display preferences for macOS
 (setq ns-use-native-fullscreen t)
 (setq ns-use-thin-smoothing t)
 (setq ns-pop-up-frames nil)

 ;; Avoid dired issues specific to macOS
 (setq dired-use-ls-dired nil))

Development

When in programming mode, I bind C-c C-c to run 'compile. This is a huge time-saver when working on projects - just hit the key combo and watch your code build.

(use-package prog-mode
  :bind (:map prog-mode-map
              ("C-c C-c" . compile)
              ;; These conflict with flycheck
              ;; ("M-n" . highlight-symbol-next)
              ;; ("M-p" . highlight-symbol-prev)
              )
  :config
  (setq show-trailing-whitespace t)
  :hook ((prog-mode . display-line-numbers-mode)))

Terminals

Let’s try vterm to see if we like it. It’s supposedly better than the built-in term/ansi-term because it’s a fully-fledged terminal emulator that handles escape sequences properly.

(use-package vterm
  :defer t
  :ensure t)

Projectile Mode

Bind projectile to C-c p and enable by default.

(use-package projectile
  :ensure t
  :diminish projectile-mode
  :custom
  (projectile-project-search-path '("~/p/"))
  (projectile-completion-system 'ivy)
  (projectile-enable-caching t)
  (projectile-indexing-method 'alien)
  (projectile-sort-order 'recently-active)
  :bind-keymap ("C-c p" . projectile-command-map)
  :bind (:map projectile-command-map
              ("C" . projectile-invalidate-cache))
  :hook (after-init . projectile-mode))

Company

(use-package company
  :ensure t
  :bind
  (:map company-active-map
        ("C-n". company-select-next)
        ("C-p". company-select-previous)
        ("M-<". company-select-first)
        ("M->". company-select-last))
  :hook (prog-mode . company-mode))

Magit

C-c is reserved for the user. Add a more friendly binding for magit-file-dispatch

(use-package magit
  :ensure t
  :defer t
  :commands (magit-status magit-file-dispatch)
  :bind
  ("C-x g" . magit-status)
  ("C-c g" . magit-file-dispatch))

Paredit

Some handy dandy paredit shortcuts

On Mac, ^-left and ^-right are bound to Mission Control. Go to `System Preferences > Keyboard > Shortcuts > Mission Control` and change the settings for “Move left a space” and “Move right a space” or disable them completely.

(use-package paredit
  :ensure t
  :bind
  (:map paredit-mode-map
        ("C-<right>" . paredit-forward-slurp-sexp)
        ("C-<left>" . paredit-forward-barf-sexp)
        ("C-<backspace>" . paredit-backward-kill-word)
        ("RET" . nil))
  :hook ((cider-repl-mode
          clojure-mode
          emacs-lisp-mode
          eval-expression-minibuffer-setup
          ielm-mode
          inf-clojure-mode-hook
          lisp-interaction-mode
          lisp-mode
          scheme-mode) . paredit-mode))

Clojure

I don’t like my cider to be bleeding edge since it’s caused compatibility problems in the past so pin it to melpa-stable.

(use-package clojure-mode
  :ensure t
  :defer t
  :config
  (setq clojure-align-forms-automatically t)
  (eldoc-add-command 'paredit-backward-delete 'paredit-close-round)
  (add-hook 'clojure-mode-hook #'subword-mode))

(use-package inf-clojure
  :ensure t
  :defer t
  :config
  (add-hook 'inf-clojure-mode-hook #'rainbow-delimiters-mode))

(use-package cider
  :ensure t
  :defer t
  :commands cider-jack-in
  :custom
  (nrepl-log-messages t)
  (cider-repl-use-clojure-font-lock t)
  (cider-repl-display-help-banner nil))

I have long since used this key binding to jack into a repl. My fingers are programmed this way.

(keymap-global-set "C-c C-j" 'cider-jack-in)

Elisp

(add-hook 'emacs-lisp-mode-hook #'eldoc-mode)

Org Mode

I almost exclusively use C-j in place of hitting the enter key. The problem is that it’s bound to the org-return-indent function. This is very annoying when you are in org-mode. So instead of trying to remap my brain, I’ll remap it to newline.

(use-package org-bullets
  :ensure t
  :after org
  :hook (org-mode . org-bullets-mode))

(use-package ob-rust
  :ensure t)

(use-package org
  :ensure t
  :bind
  (:map
   org-mode-map
   ("C-j" . org-return)
   ("C-c ]" . org-ref-insert-link)
   ("C-c l" . org-store-link)
   ("C-c a" . org-agenda)
   ("C-c c" . org-capture))
  :custom
  (org-hide-emphasis-markers t)
  :config
  (when window-system
    (let* ((variable-tuple
            (cond ((x-list-fonts "ETBembo")         '(:font "ETBembo"))
                  ((x-list-fonts "Source Sans Pro") '(:font "Source Sans Pro"))
                  ((x-list-fonts "Lucida Grande")   '(:font "Lucida Grande"))
                  ((x-list-fonts "Verdana")         '(:font "Verdana"))
                  ((x-family-fonts "Sans Serif")    '(:family "Sans Serif"))
                  (nil (warn "Cannot find a Sans Serif Font.  Install Source Sans Pro."))))
           (base-font-color     (face-foreground 'default nil 'default))
           (headline           `(:inherit default :weight bold :foreground ,base-font-color)))

      (custom-theme-set-faces
       'user
       `(org-level-8 ((t (,@headline ,@variable-tuple))))
       `(org-level-7 ((t (,@headline ,@variable-tuple))))
       `(org-level-6 ((t (,@headline ,@variable-tuple))))
       `(org-level-5 ((t (,@headline ,@variable-tuple))))
       `(org-level-4 ((t (,@headline ,@variable-tuple :height 1.1))))
       `(org-level-3 ((t (,@headline ,@variable-tuple :height 1.25))))
       `(org-level-2 ((t (,@headline ,@variable-tuple :height 1.5))))
       `(org-level-1 ((t (,@headline ,@variable-tuple :height 1.75))))
       `(org-document-title ((t (,@headline ,@variable-tuple :height 2.0 :underline nil)))))))
  (turn-on-auto-fill)
  (org-babel-do-load-languages
   'org-babel-load-languages '((rust . t)
                               (shell . t))))

Exporting to PDF

In order to export to PDF, I choose to use basictex and install packages only when they are missing.

brew reinstall --cask basictex
sudo tlmgr update --self
sudo tlmgr install wrapfig
sudo tlmgr install capt-of

JavaScript

(use-package js
  :ensure t
  :config
  (setq js-indent-level 2))

CSS

(use-package css-mode
  :ensure t
  :config
  (setq css-indent-level 2))

Flycheck

(use-package flycheck
  :ensure t
  :init (global-flycheck-mode)
  :bind (:map flycheck-mode-map
              ("M-n" . flycheck-next-error) ; optional but recommended error navigation
              ("M-p" . flycheck-previous-error))
  :hook ((prog-mode . flycheck-mode)
         (text-mode . flycheck-mode)))

Flyspell

(use-package flyspell
  :ensure t
  :defer t
  :commands (flyspell-mode flyspell-prog-mode)
  :custom
  (flyspell-issue-welcome-flag nil)
  (flyspell-issue-message-flag nil)
  (flyspell-mark-duplications-flag nil)
  (ispell-program-name "aspell")
  (ispell-list-command "list")
  :bind (:map flyspell-mouse-map
              ([down-mouse-3] . flyspell-correct-word)
              ([mouse-3] . undefined))
  :hook (((text-mode org-mode markdown-mode) . flyspell-mode)
         (prog-mode . flyspell-prog-mode)))

Markdown

(use-package ox-gfm
  :ensure t)

(use-package markdown-mode
  :ensure t
  :mode (("\\.md\\'" . gfm-mode)
         ("\\.markdown\\'" . gfm-mode)))

Git

Use diff-mode when editing a git commit message

(add-to-list 'auto-mode-alist '("COMMIT_EDITMSG$" . diff-mode))

Web Development

Tree-sitter is a game-changer for syntax highlighting and code navigation. It’s a parser generator tool that builds concrete syntax trees for source files, which enables much more accurate syntax highlighting and structural editing than regex-based modes. Emacs 29+ has built-in support for it.

(use-package treesit
  :mode (("\\.tsx\\'" . tsx-ts-mode)
         ("\\.js\\'"  . typescript-ts-mode)
         ("\\.mjs\\'" . typescript-ts-mode)
         ("\\.mts\\'" . typescript-ts-mode)
         ("\\.cjs\\'" . typescript-ts-mode)
         ("\\.ts\\'"  . typescript-ts-mode)
         ("\\.jsx\\'" . tsx-ts-mode)
         ("\\.json\\'" .  json-ts-mode)
         ("\\.yaml\\'" .  yaml-ts-mode)
         ("\\.Dockerfile\\'" . dockerfile-ts-mode))
  :preface
  (defun os/setup-install-grammars ()
    "Install Tree-sitter grammars if they are absent.
This function checks if each grammar is already installed before downloading it,
which saves time during initialization."
    (interactive)
    (dolist (grammar
             '((css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.20.0"))
               (scss . ("https://github.com/serenadeai/tree-sitter-scss"))
               (bash "https://github.com/tree-sitter/tree-sitter-bash")
               (html . ("https://github.com/tree-sitter/tree-sitter-html" "v0.20.1"))
               (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.21.2" "src"))
               (json . ("https://github.com/tree-sitter/tree-sitter-json" "v0.20.2"))
               (python . ("https://github.com/tree-sitter/tree-sitter-python" "v0.20.4"))
               (go "https://github.com/tree-sitter/tree-sitter-go" "v0.20.0")
               (markdown "https://github.com/ikatyang/tree-sitter-markdown")
               (make "https://github.com/alemuller/tree-sitter-make")
               (elisp "https://github.com/Wilfred/tree-sitter-elisp")
               (cmake "https://github.com/uyha/tree-sitter-cmake")
               (c "https://github.com/tree-sitter/tree-sitter-c")
               (cpp "https://github.com/tree-sitter/tree-sitter-cpp")
               (toml "https://github.com/tree-sitter/tree-sitter-toml")
               (tsx . ("https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "tsx/src"))
               (typescript . ("https://github.com/tree-sitter/tree-sitter-typescript" "v0.20.3" "typescript/src"))
               (yaml . ("https://github.com/ikatyang/tree-sitter-yaml" "v0.5.0"))))
      (add-to-list 'treesit-language-source-alist grammar)
      ;; Only install `grammar' if we don't already have it
      ;; installed. However, if you want to *update* a grammar then
      ;; this obviously prevents that from happening.
      (unless (treesit-language-available-p (car grammar))
        (treesit-install-language-grammar (car grammar)))))

  ;; Remap traditional modes to tree-sitter modes
  ;; This is a huge improvement for syntax highlighting
  (dolist (mapping
           '((bash-mode . bash-ts-mode)
             (c++-mode . c++-ts-mode)
             (c-mode . c-ts-mode)
             (c-or-c++-mode . c-or-c++-ts-mode)
             (css-mode . css-ts-mode)
             (js-json-mode . json-ts-mode)
             (js-mode . typescript-ts-mode)
             (js2-mode . typescript-ts-mode)
             (json-mode . json-ts-mode)
             (python-mode . python-ts-mode)
             (scss-mode . scss-ts-mode)
             (sh-base-mode . bash-ts-mode)
             (sh-mode . bash-ts-mode)
             (typescript-mode . typescript-ts-mode)))
    (add-to-list 'major-mode-remap-alist mapping))
  :config
  (os/setup-install-grammars))

Language Server Protocol (LSP)

LSP is a game-changer for IDE-like features in Emacs. It provides code completion, go-to-definition, find references, and much more. I use it for most of my programming languages.

(use-package ivy
  :ensure t
  :hook (after-init . ivy-mode))

(use-package counsel
  :ensure t
  :after ivy
  :hook (ivy-mode . counsel-mode))

(use-package lsp-ivy
  :ensure t
  :commands lsp-ivy-workspace-symbol)

(use-package lsp-ui
  :ensure t
  :commands lsp-ui-mode
  :hook (lsp-mode . lsp-ui-mode)
  :config
  (setq lsp-ui-doc-enable nil))

(use-package lsp-mode
  :ensure t
  :commands (lsp lsp-deferred)
  :hook
  ((tsx-ts-mode typescript-ts-mode js-ts-mode python-ts-mode) . lsp-deferred)
  :preface
  (setq read-process-output-max (* 10 1024 1024)  ; 10MB - Increase read chunk size for better performance
        ;; gc-cons-threshold 200000000           ; Uncomment to increase GC threshold
        ;; lsp-use-plists t                      ; Uncomment to use plists instead of hashtables
        )

  ;; LSP-booster integration for better performance
  (defun lsp-booster--advice-json-parse (old-fn &rest args)
    "Try to parse bytecode instead of json.
This dramatically improves performance when receiving large JSON responses."
    (or
     (when (equal (following-char) ?#)
       (let ((bytecode (read (current-buffer))))
         (when (byte-code-function-p bytecode)
           (funcall bytecode))))
     (apply old-fn args)))

  (defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
    "Prepend emacs-lsp-booster command to LSP server command.
This uses the external emacs-lsp-booster tool to speed up JSON parsing."
    (let ((orig-result (funcall old-fn cmd test?)))
      (if (and (not test?)                             ;; for check lsp-server-present?
               (not (file-remote-p default-directory)) ;; see lsp-resolve-final-command, it would add extra shell wrapper
               lsp-use-plists
               (not (functionp 'json-rpc-connection))  ;; native json-rpc
               (executable-find "emacs-lsp-booster"))
          (progn
            (when-let ((command-from-exec-path (executable-find (car orig-result))))  ;; resolve command from exec-path (in case not found in $PATH)
              (setcar orig-result command-from-exec-path))
            (message "Using emacs-lsp-booster for %s!" orig-result)
            (cons "emacs-lsp-booster" orig-result))
        orig-result)))
  :init
  ;; Apply our advice functions to speed up LSP
  (advice-add (if (progn (require 'json)
                         (fboundp 'json-parse-buffer))
                  'json-parse-buffer
                'json-read)
              :around
              #'lsp-booster--advice-json-parse)
  (advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command))

Rust

Rust is my language du jour. It’s slowly becoming my favourite programming language. The rustic package provides excellent integration with rust-analyzer (via LSP) and cargo.

(use-package rustic
  :defer t
  :ensure t
  :bind (:map rustic-mode-map
              ("M-j" . lsp-ui-imenu)
              ("M-?" . lsp-find-references)
              ("C-c C-c l" . flycheck-list-errors)
              ("C-c C-c a" . lsp-execute-code-action)
              ("C-c C-c r" . lsp-rename)
              ("C-c C-c q" . lsp-workspace-restart)
              ("C-c C-c Q" . lsp-workspace-shutdown)
              ("C-c C-c s" . lsp-rust-analyzer-status))
  :custom
  (rustic-compile-command "cargo b --release")
  (rustic-default-clippy-arguments "--all-targets --all-features -- -D warnings")
  (rust-format-on-save t)
  (rustic-ansi-faces ["black" "#bf616a" "#a3be8c" "#ecbe7b" "#2257a0" "#b48ead" "#4db5bd" "white"]))

ELISP

An Interactive Emacs Lisp Mode (IELM) gives you an Emacs Lisp shell.

(use-package ielm
  :ensure t
  :bind
  (:map ielm-map
        ("C-m" . 'ielm-return)
        ("<return>" . 'ielm-return))
  :config
  (add-hook 'ielm-mode-hook #'rainbow-delimiters-mode)
  (add-hook 'ielm-mode-hook #'paredit-mode))

OCaml

(use-package tuareg
  :ensure t)

Nix

(use-package nixpkgs-fmt
  :ensure t)

(use-package nix-mode
  :mode ("\\.nix\\'" "\\.nix.in\\'")
  :ensure t
  :bind
  (:map nix-mode-map
        ("C-c C-f" . nixpkgs-fmt))
  :config
  (nixpkgs-fmt-on-save-mode))

(use-package nix-drv-mode
  :ensure nix-mode
  :mode "\\.drv\\'")

(use-package nix-shell
  :ensure nix-mode
  :commands (nix-shell-unpack nix-shell-configure nix-shell-build))

(use-package nix-repl
  :ensure nix-mode
  :commands (nix-repl))

Terraform

(use-package terraform-mode
  :ensure t)

Misc

(use-package csv-mode
  :ensure t)

(use-package just-mode
  :ensure t
  :config
  (setq just-indent-offset 2))

(use-package dockerfile-mode
  :ensure t)

(use-package yaml-mode
  :ensure t)

(use-package bnf-mode
  :ensure t)

(use-package htmlize
  :ensure t)

(use-package ag
  :ensure t)

(use-package string-inflection
  :ensure t)

(use-package direnv
  :ensure t)

(use-package yasnippet
  :ensure t
  :diminish yas-minor-mode
  :commands (yas-minor-mode yas-global-mode)
  :hook ((prog-mode . yas-minor-mode)
         (org-mode . yas-minor-mode)))

Reset the garbage collection threshold.

(setq gc-cons-threshold 800000)

About

This is my emacs, there are many like it, but this one is mine...

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published