My Emacs Configuration

Introduction

This Org Mode document contains my configuration for Emacs, which is currently a work in progress. Changes made to the configuration here are automatically tangled when saved. As such, all configuration changes should be made here.

Early Init File

The configuration here gets placed in an early init file to prevent certain actions from occuring before getting to the main init file.

Since I am using straight.el The first thing we need to do is stop package.el from loading packages.

;; Prevent package.el from loading packages
(setq package-enable-at-startup nil)

Here, we make sure we’re starting with a minimal user interface.

;; Disable the menu bar
(menu-bar-mode -1)

;; Disable the tool bar
(tool-bar-mode -1)

;; Disable the scroll bar
(scroll-bar-mode -1)

;; Disable the splash screen
(setq inhibit-splash-screen t)

Primary Init File

Here we specify the main Emacs configuration.

Init File Header

Add a note to the header to remind me not to edit that file.

;; NOTE: init.el is generated from emacs.org. Please edit that file instead!

Straight Package Management

I chose to use straight.el for my package management. I like the principles of the project, and changing package managers will help me stop simply copying other people’s configuration without trying to figure it out myself.

;; Bootstrap code needed to download and build straight.el
(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/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)

;; Automatically install packages without needing to specify so
(setq straight-use-package-by-default t)

Clean Up Folders

no-littering avoids excessive clutter in the user-emacs-directory.

;; Use no-littering to automatically set common paths to the user-emacs-directory
(use-package no-littering)

;; no-littering doesn't set this by default so we must place auto save
;; files in the same path as it uses for sessions
(setq auto-save-file-name-transforms
      `((".*" ,(no-littering-expand-var-file-name "auto-save/") t)))

;; Keep customization settings in a temporary file
(setq custom-file
      (if (boundp 'server-socket-dir)
          (expand-file-name "custom.el" server-socket-dir)
        (expand-file-name (format "emacs-custom-%s.el" (user-uid)) temporary-file-directory)))
(load custom-file t)

User Interface Configuration

Basic Setup

Here we make some general configuration tweaks to the user interface, such as transparency, line numbers, etc..

;; Use pixel wise frame size
(setq frame-resize-pixelwise t)

;; Make frame transparency overridable
(defvar jdw/frame-transparency '(97 . 97))

; Disable tooltips
(tooltip-mode -1)

; Give some breathing room
(set-fringe-mode 10)

;; Set up the visible bell
(setq visible-bell t)

;; Enable column and line number modes
(column-number-mode)
(global-display-line-numbers-mode t)

;; Set frame transparency
(set-frame-parameter (selected-frame) 'alpha jdw/frame-transparency)
(add-to-list 'default-frame-alist `(alpha . ,jdw/frame-transparency))

;; Disable line numbers for some modes
(dolist (mode '(org-mode-hook
                eshell-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode 0))))

Font Configuration

I am using the Fira Code fonts. Let’s set font sizes for various fonts.

NOTE: The font size will likely need to change from system to system.

;; Specify the font size
(defvar jdw/default-font-size 120)
(defvar jdw/default-variable-font-size 140)
(defvar jdw/default-variable-font-weight 'regular)

;; Set font faces
(defun jdw/set-font-faces ()

  ;; Set the default face
  (set-face-attribute 'default nil
                      :font "Fira Code Retina"
                      :height jdw/default-font-size)

  ;; Set the fixed pitch face
  (set-face-attribute 'fixed-pitch nil
                      :font "Fira Code Retina"
                      :height jdw/default-font-size)

  ;; Set the variable pitch face
  (set-face-attribute 'variable-pitch nil
                      :font "Fira Sans"
                      :height jdw/default-variable-font-size
                      :weight jdw/default-variable-font-weight)

  ;; Make commented text and keywords italics.
  ;; Your font must have an italic face available.
  (set-face-attribute 'font-lock-comment-face nil
                      :slant 'italic)
  (set-face-attribute 'font-lock-keyword-face nil
                      :slant 'italic))

;; Uncomment the following line if line spacing needs adjusting.
(setq-default line-spacing 0.12)

;; Needed if using emacsclient. Otherwise, your fonts will be smaller than expected.
(add-to-list 'default-frame-alist '(font . "Fira Code Retina-12"))

;; Changes certain keywords to symbols, such as lamda!
(setq global-prettify-symbols-mode t)

;; Call font face function differently if using daemon
(if (daemonp)
    (add-hook 'after-make-frame-functions
              (lambda (frame)
                (with-selected-frame frame
                  (jdw/set-font-faces))))
  (jdw/set-font-faces))

Color Theme

doom-themes is a great set of themes with a lot of variety and support for many different Emacs modes, and the screenshots can help decide without manually booting up each theme to decide.

;; Use doom-themes for color themes
(use-package doom-themes
  :config

  ;; Enable bold and italics
  (setq doom-themes-enable-bold t
        doom-themes-enable-italic t)

  ;; Load the Dracula theme
  (load-theme 'doom-gruvbox t)

  ;; Enable flashing mode-line on errors
  (doom-themes-visual-bell-config)

  ;; Corrects (and improves) org-mode's native fontification.
  (doom-themes-org-config))

Modeline

doom-modeline is a very attractive and rich (yet still minimal) mode line configuration for Emacs. The default configuration is quite good but you can check out the configuration options for more things you can enable or disable.

NOTE: The first time you load your configuration on a new machine, you’ll need to run M-x all-the-icons-install-fonts so that mode line icons display correctly.

;; Use all-the-icons to view symbols in the modeline
(use-package all-the-icons)

;; Use doom-modeline for a prettier modeline
(use-package doom-modeline
  :hook (after-init . doom-modeline-mode)
  :custom ((doom-modeline-height 30)
           (doom-modeline-icon t)))

Dashboard

I kind of like the doom-emacs dashboard, so let’s try using out dashboard.

;; Install nerd icons
(use-package nerd-icons)

(use-package dashboard
  :ensure t
  :config
  (dashboard-setup-startup-hook)
  :custom

  ;; Set the title
  (dashboard-banner-logo-title "Welcome to Emacs Dashboard!")

  ;; Set the banner
  (dashboard-startup-banner 1)

  ;; Limit items that appear and specify what appears
  (dashboard-items '((recents  . 5)
                   (bookmarks . 5)
                   (projects . 5)
                   (agenda . 5)))

  ;; Display icons
  (dashboard-display-icons-p t)
  (dashboard-icon-type 'nerd-icons)
  (dashboard-set-heading-icons t)
  (dashboard-set-file-icons t)

)

Leader Key Bindings with General

General allows leader key binding, which I will choose as SPC.

(use-package general
  :config
  (general-evil-setup t)

  (general-create-definer jdw/leader-key-def
                          :keymaps '(normal insert visual emacs)
                          :prefix "SPC"
                          :global-prefix "C-SPC")

  (general-create-definer jdw/ctrl-c-keys
                          :prefix "C-c"))


;; Files
(jdw/leader-key-def
  "f"   '(:ignore t :which-key "files")
  "ff" 'find-file
  "fs" 'save-buffer)

;; Search
(jdw/leader-key-def 
  "s" '(:ignore t :which-key "search")
  "sb" 'consult-line
  "sr" 'query-replace)

;; Toggles
(jdw/leader-key-def
  "t"  '(:ignore t :which-key "toggles")
  "tt" '(consult-theme :which-key "choose theme"))

Evil Mode

Evil is a vi layer for Emacs. It changes a lot of key bindings and other features to be more like vi. I rarely use vi, but I do prefer the typical key bindings.

;; Add hooks for evil
(defun jdw/evil-hook ()
  (dolist (mode '(custom-mode
                  eshell-mode
                  git-rebase-mode
                  term-mode)) 
    (add-to-list 'evil-emacs-state-modes mode)))

;; Remind me to get back to the home row of keys
(defun jdw/dont-use-arrows ()
  (interactive)
  (message "Get back on the home row!"))

;; Use vi-like undo state preservation
(use-package undo-tree
  :init
  (global-undo-tree-mode 1)
  :config
  ;; Prevent undo tree files from polluting your git repo
  (setq undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo"))))

;; Use evil
(use-package evil
  :init
  (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)
  :config
  (add-hook 'evil-mode-hook 'jdw/evil-hook)
  (evil-mode 1)
  (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)

  ;; Disable arrow keys in normal and visual modes
  (define-key evil-normal-state-map (kbd "<left>") 'jdw/dont-use-arrows)
  (define-key evil-normal-state-map (kbd "<right>") 'jdw/dont-use-arrows)
  (define-key evil-normal-state-map (kbd "<down>") 'jdw/dont-use-arrows)
  (define-key evil-normal-state-map (kbd "<up>") 'jdw/dont-use-arrows)
  (evil-global-set-key 'motion (kbd "<left>") 'jdw/dont-use-arrows)
  (evil-global-set-key 'motion (kbd "<right>") 'jdw/dont-use-arrows)
  (evil-global-set-key 'motion (kbd "<down>") 'jdw/dont-use-arrows)
  (evil-global-set-key 'motion (kbd "<up>") 'jdw/dont-use-arrows)

  ;; Set initial states
  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal))

;; Load in additional evil keybindings
(use-package evil-collection
  :after evil
  :init
  (setq evil-collection-company-use-tng nil)  ;; Is this a bug in evil-collection?
  :custom
  (evil-collection-outline-bind-tab-p nil)
  :config
  (setq evil-collection-mode-list
        (remove 'lispy evil-collection-mode-list))
  (evil-collection-init))

Evil-Org-Mode is an extension of Evil into Org-Mode.

;; Use evil-org for evil extensions to org-mode
(use-package evil-org
  :after org
  :hook ((org-mode . evil-org-mode)
         (org-agenda-mode . evil-org-mode)
         (evil-org-mode . (lambda () (evil-org-set-key-theme
                                      '(navigation todo insert textobjects additional)))))
  :config
  (require 'evil-org-agenda)
  (evil-org-agenda-set-keys))

;; Add to leader key bindings
(jdw/leader-key-def
 "o"   '(:ignore t :which-key "org mode")
 "oi"  '(:ignore t :which-key "insert")
 "oil" '(org-insert-link :which-key "insert link")
 "on"  '(org-toggle-narrow-to-subtree :which-key "toggle narrow")
 "oa"  '(org-agenda :which-key "status")
 "ot"  '(org-todo-list :which-key "todos")
 "oc"  '(org-capture t :which-key "capture")
 "ox"  '(org-export-dispatch t :which-key "export"))

Which Key

which-key is a useful UI panel that appears when you start pressing any key binding in Emacs to offer you all possible completions for the prefix. For example, if you press C-c (hold control and press the letter c), a panel will appear at the bottom of the frame displaying all of the bindings under that prefix and which command they run. This is very useful for learning the possible key bindings in the mode of your current buffer.

(use-package which-key
  :defer 0
  :diminish which-key-mode
  :config
  (which-key-mode)
  (setq which-key-idle-delay 1))

Completion System

  • Veritico Completions

    vertico is a minimalist vertical completion interface that plays well with other packages.

    (defun jdw/minibuffer-backward-kill (arg)
      "When minibuffer is completing a file name delete up to parent
    folder, otherwise delete a word"
      (interactive "p")
      (if minibuffer-completing-file-name
          ;; Borrowed from https://github.com/raxod502/selectrum/issues/498#issuecomment-803283608
          (if (string-match-p "/." (minibuffer-contents))
              (zap-up-to-char (- arg) ?/)
            (delete-minibuffer-contents))
        (backward-kill-word arg)))
    
    ;; Enable vertico for completions
    (use-package vertico
      :bind (:map vertico-map
                  ("C-j" . vertico-next)
                  ("C-k" . vertico-previous)
                  ("C-f" . vertico-exit)
                  :map minibuffer-local-map
                  ("M-h" . jdw/minibuffer-backward-kill))
      :custom
      (vertico-cycle t)
      :init
      (vertico-mode))
  • Save Mini-Buffer History

    Use the internal savehist package to presever the mini-buffer history.

    ;; Preserve minibuffer history with savehist 
    (use-package savehist
      :config
      (setq history-length 25)
      (savehist-mode 1))
  • Orderless Candidate Matching

    orderless enables space separated candidate matching for all components of the completions.

    ;; Enable orderless for completion style
    (use-package orderless
      :init
      (setq completion-styles '(orderless)
            completion-category-defaults nil
            completion-category-overrides '((file (styles partial-completion)))))
  • Marginalia Mini-Buffer Annotations

    marginalia enables completion annotations in the minibuffer.

    ;; Enable completion annotations with marginalia
    (use-package marginalia
      :after vertico
      :custom
      (marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
      :init
      (marginalia-mode))
  • Corfu Region Completion

    corfu enhances completion at point in a minimalist approach.

    ;; Enhance completion at point with corfu
    (use-package corfu
      :bind (:map corfu-map
                  ("C-j" . corfu-next)
                  ("C-k" . corfu-previous)
                  ("C-f" . corfu-insert))
      :custom
      (corfu-cycle t)
      :config
      (global-corfu-mode))
  • Search and Navigation with Consult

    consult provides minimal search and navigation commands.

    ;; Use consult for search/navigation
    (use-package consult
      :demand t
      :bind (("C-s" . consult-line)
             ("C-M-l" . consult-imenu)
             :map minibuffer-local-map
             ("C-r" . consult-history))
      :custom
      (completion-in-region-function #'consult-completion-in-region))
  • Completion Actions with Embark

    Embark allows completion actions, among other things. There’s a System Crafters video to get started, and he mentions some other good resources as well:

    ;; Use Embark for completion actions 
    (use-package embark
      :bind (("C-S-a" . embark-act)
             :map minibuffer-local-map
             ("C-d" . embark-act))
      :config
    
    ;; Show Embark actions via which-key
    (setq embark-action-indicator
          (lambda (map)
            (which-key--show-keymap "Embark" map nil nil 'no-paging)
            #'which-key--hide-popup-ignore-command)
          embark-become-indicator embark-action-indicator))
    
    ;; Use embark-consult for consult integration
    (use-package embark-consult)

Auto-Save Changed Files

super-save automatically auto-saves changed files when certain events occur, such as buffer changes.

(use-package super-save
  :defer 1
  :diminish super-save-mode
  :config
  (super-save-mode +1)
  (setq super-save-auto-save-when-idle t))

Highlight Keywords

hl-todo is a useful tool to highlight keywords like TODO or BUG.

;; Use hl-todo to highlight keywords
(use-package hl-todo
  :init
  (global-hl-todo-mode))

Rainbow Delimiters

rainbow-delimiters is useful in programming modes because it colorizes nested parentheses and brackets according to their nesting depth. This makes it a lot easier to visually match parentheses in Emacs Lisp code without having to count them yourself.

;; Use rainbow colors for things like parentheses and brackets
(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

Rainbow Mode

Rainbow Mode enables visualization of color codes like #BD93F9 in Emacs.

;; Use rainbow mode to see color codes highlighted
(use-package rainbow-mode
  :hook prog-mode org-mode)

Flycheck Syntax Checking

Flycheck provides on the fly syntax checking.

(use-package flycheck
  :defer t
  :hook (lsp-mode . flycheck-mode))

Org Mode

Org Mode is one of the hallmark features of Emacs. It is a rich document editor, project planner, task and time tracker, blogging engine, and literate coding utility all wrapped up in one package.

Better Font Faces

The jdw/org-font-setup function configures various text faces to tweak the sizes of headings and use variable width fonts in most cases so that it looks more like we’re editing a document in org-mode. We switch back to fixed width (monospace) fonts for code blocks and tables so that they display correctly.

;; Function to call for specifying org-mode fonts
(defun jdw/org-font-setup ()

  ;; Replace list hyphen with dot
  (font-lock-add-keywords 'org-mode
                          '(("^ *\\([-]\\) "
                             (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))

  ;; Set faces for heading levels
  (dolist (face '((org-level-1 . 1.1)
                  (org-level-2 . 1.1)
                  (org-level-3 . 1.1)
                  (org-level-4 . 1.1)
                  (org-level-5 . 1.1)
                  (org-level-6 . 1.1)
                  (org-level-7 . 1.1)
                  (org-level-8 . 1.1)))
    (set-face-attribute (car face) nil :font "Fira Sans" :weight 'regular :height (cdr face)))

  ;; Ensure that anything that should be fixed-pitch in Org files appears that way
  (set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-table nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-formula nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-code nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-table nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch)
  (set-face-attribute 'line-number nil :inherit 'fixed-pitch)
  (set-face-attribute 'line-number-current-line nil :inherit 'fixed-pitch)

  ;; Change LaTeX font size
  (setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5)))

Basic Config

This section contains the basic configuration for org-mode.

;; Function for basic org-mode setup
(defun jdw/org-mode-setup ()
  (org-indent-mode)
  (variable-pitch-mode 1)
  (visual-line-mode 1))

;; Load the org package
(use-package org
  ;;:pin org
  :commands (org-capture org-agenda)
  :hook (org-mode . jdw/org-mode-setup)
  :bind (("C-c a" . org-agenda))
  :config

  ;; Hide emphasis markers on formatted text
  (setq org-hide-emphasis-markers t)

  ;; Specify elipsis symbol
  (setq org-ellipsis " ▾")

  ;; Change org-mode logging
  (setq org-agenda-start-with-log-mode t)
  (setq org-log-done 'time)
  (setq org-log-into-drawer t)

  ;; Specify files to build org-agenda
  (setq org-agenda-files
        '("~/Documents/Org/inbox.org"
          "~/Documents/Org/todo.org"))

  ;; Track habits with org-habit
  (require 'org-habit)
  (add-to-list 'org-modules 'org-habit)
  (setq org-habit-graph-column 60)

  ;; Customize todo keywords
  (setq org-todo-keywords
    '((sequence "TODO(t)" "NEXT(n)" "EVENT(e)" "|" "DONE(d!)")))

  ;; Customize tags
  (setq org-tag-alist
    '((:startgroup)
       ; Put mutually exclusive tags here
       (:endgroup)
       ("@home" . ?H)
       ("@work" . ?W)
       ("errand" . ?e)
       ("agenda" . ?a)
       ("chore" .?c)
       ("idea" . ?i)))

  ;; Place org agenda tags column
  (setq org-agenda-tags-column 0)

  ;; Only one space after a tag
  (setq org-tags-column 0)

  ;; Set up org-mode fonts
  (jdw/org-font-setup)

  ;; Evil implementiation
  (evil-define-key '(normal insert visual) org-mode-map (kbd "C-j") 'org-next-visible-heading)
  (evil-define-key '(normal insert visual) org-mode-map (kbd "C-k") 'org-previous-visible-heading)
  (evil-define-key '(normal insert visual) org-mode-map (kbd "M-j") 'org-metadown)
  (evil-define-key '(normal insert visual) org-mode-map (kbd "M-k") 'org-metaup))

Nicer Heading Bullets

org-bullets replaces the heading stars in org-mode buffers with nicer looking characters that you can control.

;; Use nicer looking bullets for org-mode
(use-package org-bullets
  :hook (org-mode . org-bullets-mode)
  :config
  (setq org-hide-leading-stars t)
  :custom
  (org-bullets-bullet-list '("◉" "○" "●" "○" "●" "○" "●")))

Center Org Buffers

We use visual-fill-column to center org-mode buffers for a more pleasing writing experience as it centers the contents of the buffer horizontally to seem more like you are editing a document. This is really a matter of personal preference so you can remove the block below if you don’t like the behavior.

;; Specify visual-fill centering settings
(defun jdw/org-mode-visual-fill ()
  (setq visual-fill-column-width 120
        visual-fill-column-center-text t)
  (visual-fill-column-mode 1))

(defun jdw/dashboard-mode-visual-fill ()
  (setq visual-fill-column-width 100
        visual-fill-column-center-text t)
  (visual-fill-column-mode 1))

;; Use visual-fill-column to center org-mode buffers
(use-package visual-fill-column
  :hook ((org-mode . jdw/org-mode-visual-fill)
          (dashboard-mode . jdw/dashboard-mode-visual-fill)))

Configure Babel Languages

To execute or export code in org-mode code blocks, you’ll need to set up org-babel-load-languages for each language you’d like to use. This page documents all of the languages that you can use with org-babel.

;; Specify org-babel languages
(with-eval-after-load 'org
  (org-babel-do-load-languages
      'org-babel-load-languages
      '((emacs-lisp . t)
        (python . t)
        (R . t)
        (lua . t)
        (shell . t)))
  (push '("conf-unix" . conf-unix) org-src-lang-modes))

Structure Templates

Org Mode’s structure templates feature enables you to quickly insert code blocks into your Org files in combination with org-tempo by typing < followed by the template name like el or py and then press TAB. For example, to insert an empty emacs-lisp block below, you can type <el and press TAB to expand into such a block.

You can add more src block templates below by copying one of the lines and changing the two strings at the end, the first to be the template name and the second to contain the name of the language as it is known by Org Babel.

;; Apply structure templates to quickly insert code blocks in org files
(with-eval-after-load 'org

  ;; This is needed as of Org 9.2
  (require 'org-tempo)

  ;; Specify structure templates
  (add-to-list 'org-structure-template-alist '("sh" . "src shell"))
  (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
  (add-to-list 'org-structure-template-alist '("py" . "src python"))
  (add-to-list 'org-structure-template-alist '("r" . "src R"))
  (add-to-list 'org-structure-template-alist '("lua" . "src lua")))

Auto-tangle Configuration Files

This snippet adds a hook to org-mode buffers so that jdw/org-babel-tangle-config gets executed each time such a buffer gets saved. This function checks to see if the file being saved is in the directory ~/.dotfiles/, and if so, tangles the file to the file path specified in the header arguments for the code block to tangle.

;; Automatically tangle our Emacs.org config file when we save it
(defun jdw/org-babel-tangle-config ()

  ;; Check when the buffer file is in my dot-file directory
  (when (string-equal (file-name-directory (buffer-file-name))
                      (expand-file-name "~/.dotfiles/"))

    ;; Dynamic scoping to the rescue
    (let ((org-confirm-babel-evaluate nil))
      (org-babel-tangle))))

;; Run the function after saving
(add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook #'jdw/org-babel-tangle-config)))

Org-Roam

Org Roam is an Org Mode extension inspired by Roam and the Zettelkasten note-taking approach. I particularly like it because it solves the problem of organizing Org files, which has completely stopped several projects or throughts in their tracks before. With this approach, you just make the file, get your thoughts out there, and then move on.

;; Set up org-roam
(use-package org-roam
  :ensure t
  :custom
  (org-roam-directory "~/Documents/Org/OrgRoam")
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n i" . org-roam-node-insert))
  :config
  (org-roam-setup))

  ;; Org-Roam keys
  (jdw/leader-key-def 
    "n" '(:ignore t :which-key "org-roam")
    "nl" '(org-roam-buffer-toggle :which-key "toggle buffer")
    "nf" '(org-roam-node-find :which-key "find node")
    "ni" '(org-roam-node-insert :which-key "insert node"))
  • Org-Roam-UI

    Org-Roam-UI is a graphical front-end showing linkages for the Org-Roam files you’ve made.

    ;; Load websocket, a dependency for Org-Roam-UI
    (use-package websocket
      :after org-roam)
    
    ;; Load and configure Org-Roam-UI
    (use-package org-roam-ui
      :after org-roam
      :config
      (setq org-roam-ui-sync-theme t
            org-roam-ui-follow t
            org-roam-ui-update-on-save t
            org-roam-ui-open-on-start t))
    
    ;; Org-Roam keys
    (jdw/leader-key-def 
      "nu" '(org-roam-ui-open :which-key "open org-roam-ui"))

Development

Manage Projects with Projectile

Projectile is a project interaction library for things like finding project files and navigating through projects.

;; Enable Projectile for project interactions
(use-package projectile
  ;; Hide minor mode string in the mode-line
  :diminish projectile-mode
  :config (projectile-mode)
  :demand t
  :bind-keymap
  ("C-c p" . projectile-command-map))

Consult-Projectile incorporates Consult into Projectile.

(use-package consult-projectile
  :after projectile
  :bind (("C-M-p" . consult-projectile-find-file)))

Commenting Lines

Evil Nerd Commenter allows for commenting that acts more like I’m used to from traditional IDEs, and a bit more.

(use-package evil-nerd-commenter
  :bind ("M-/" . evilnc-comment-or-uncomment-lines))

IDE Features with lsp-mode

  • lsp-mode

    We use the excellent lsp-mode to enable IDE-like functionality for many different programming languages via “language servers” that speak the Language Server Protocol. Before trying to set up lsp-mode for a particular language, check out the documentation for your language so that you can learn which language servers are available and how to install them.

    The lsp-keymap-prefix setting enables you to define a prefix for where lsp-mode’s default keybindings will be added. I highly recommend using the prefix to find out what you can do with lsp-mode in a buffer.

    The which-key integration adds helpful descriptions of the various keys so you should be able to learn a lot just by pressing C-c l in a lsp-mode buffer and trying different things that you find there.

    One of the dependencies of lsp-mode is Spinner.el, but straight is currently looking in the wrong place for it. Let’s explicitly install it before getting started.

    ;; Install spinner, a dependency for lsp-mode
    (use-package spinner
      :straight '(spinner :type git
                          :host github
                          :repo "Malabarba/spinner.el"
                          :files (:defaults)))

    Now we can set up lsp-mode.

    ;; Build the breadcrumbs in LSP mode
    (defun jdw/lsp-mode-setup ()
      (setq lsp-headerline-breadcrumb-segments '(path-up-to-project file symbols))
      (lsp-headerline-breadcrumb-mode))
    
    ;; Enable LSP mode
    (use-package lsp-mode
    
      ;; Don't auto-load the package until we run these commands 
      :commands (lsp lsp-deferred)
    
      ;; Run our breadcrumbs function in LSP instances
      :hook (lsp-mode . jdw/lsp-mode-setup)
      :init
      (setq lsp-keymap-prefix "C-c l")
      :config
      (lsp-enable-which-key-integration t))
  • lsp-ui

    lsp-ui is a set of UI enhancements built on top of lsp-mode which make Emacs feel even more like an IDE. Check out the screenshots on the lsp-ui homepage (linked at the beginning of this paragraph) to see examples of what it can do.

    ;; Enable LSP-mode UI enhancements
    (use-package lsp-ui
      :hook (lsp-mode . lsp-ui-mode)
      :custom
      (lsp-ui-doc-position 'bottom))
  • lsp-treemacs

    lsp-treemacs provides nice tree views for different aspects of your code like symbols in a file, references of a symbol, or diagnostic messages (errors and warnings) that are found in your code.

    Try these commands with M-x:

    • lsp-treemacs-symbols - Show a tree view of the symbols in the current file
    • lsp-treemacs-references - Show a tree view for the references of the symbol under the cursor
    • lsp-treemacs-error-list - Show a tree view for the diagnostic messages in the project

    This package is built on the treemacs package which might be of some interest to you if you like to have a file browser at the left side of your screen in your editor.

    (use-package lsp-treemacs
      :after lsp)

Debugging with dap-mode

dap-mode is an excellent package for bringing rich debugging capabilities to Emacs via the Debug Adapter Protocol. You should check out the configuration docs to learn how to configure the debugger for your language. Also make sure to check out the documentation for the debug adapter to see what configuration parameters are available to use for your debug templates!

(use-package dap-mode
  ;;:custom
  ;;(lsp-enable-dap-auto-configure nil)
  :config
  ;;(dap-ui-mode 1)
  ;;(dap-tooltip-mode 1)
  (require 'dap-node)
  (dap-node-setup))

Languages

  • C/C++
    (use-package ccls
      :hook ((c-mode c++-mode objc-mode cuda-mode) .
             (lambda () (require 'ccls) (lsp))))
  • Emacs Lisp
    (add-hook 'emacs-lisp-mode-hook #'flycheck-mode)
    
    (use-package helpful
      :custom
      (counsel-describe-function-function #'helpful-callable)
      (counsel-describe-variable-function #'helpful-variable)
      :bind
      ([remap describe-function] . helpful-function)
      ([remap describe-symbol] . helpful-symbol)
      ([remap describe-variable] . helpful-variable)
      ([remap describe-command] . helpful-command)
      ([remap describe-key] . helpful-key))
    
    (jdw/leader-key-def
      "e"   '(:ignore t :which-key "eval")
      "eb"  '(eval-buffer :which-key "eval buffer"))
    
    (jdw/leader-key-def
      :keymaps '(visual)
      "er" '(eval-region :which-key "eval region"))
  • Lua
    ;; Use the Lua major mode for editing Lua code
    (use-package lua-mode
      :defer 1
      :config
      ;; Better indenting, plus avoided double indents
      (setq lua-indent-nested-block-content-align nil)
      (setq lua-indent-close-paren-align nil)
      (defun lua-at-most-one-indent (old-function &rest arguments)
        (let ((old-res (apply old-function arguments)))
          (if (> old-res lua-indent-level) lua-indent-level old-res)))
      (advice-add #'lua-calculate-indentation-block-modifier
                  :around #'lua-at-most-one-indent))
  • Python

    We use lsp-mode and dap-mode to provide a more complete development environment for Python in Emacs.

    Make sure you have the pylsp language server installed before trying lsp-mode!

    pip install --user "python-lsp-server[all]"

    There are a number of other language servers for Python so if you find that pylsp doesn’t work for you, consult the lsp-mode language configuration documentation to try the others!

    ;; Use the Python major mode for editing code
    (use-package python-mode
      :hook (python-mode . lsp-deferred)
      :custom
      (dap-python-debugger 'debugpy)
      :config
      (require 'dap-python))
  • R

    Emacs Speaks Statistics (ESS) is a great add-on built for handling a lot of statistical programs such as R, SAS, and Stata.

    ;; Use the Emacs Speaks Statistics package
    (use-package ess
      :init (require 'ess-site))
  • Fortran

    f90-mode is already a mode in base Emacs, but we still need to install the language server.

    pip install --user "fortls"

Anaconda Integration

I use Miniconda for my Python and R environment and package management. To integrate it with Emacs, we can use the conda package.

;; Configure conda package for anaconda integration
(use-package conda
  :init
  (setq conda-anaconda-home (expand-file-name "~/.miniconda"))
  (setq conda-env-home-directory (expand-file-name "~/.miniconda/")))

Magit

Magit is the best Git interface I’ve ever used. Common Git operations are easy to execute quickly using Magit’s command panel system.

;; Configure magit for git integration
(use-package magit)

;; Add leader key bindings 
(jdw/leader-key-def
 "g"   '(:ignore t :which-key "git")
 "gs"  'magit-status
 "gd"  'magit-diff-unstaged
 "gc"  'magit-branch-or-checkout
 "gl"  '(:ignore t :which-key "log")
 "glc" 'magit-log-current
 "glf" 'magit-log-buffer-file
 "gb"  'magit-branch
 "gP"  'magit-push-current
 "gp"  'magit-pull-branch
 "gf"  'magit-fetch
 "gF"  'magit-fetch-all
 "gr"  'magit-rebase)

Git Gutter

Git Gutter helps by highlighting changes to the branch.

(use-package git-gutter
  :hook ((text-mode . git-gutter-mode)
         (prog-mode . git-gutter-mode))
  :config
  (setq git-gutter:update-interval 0.02))

(use-package git-gutter-fringe
  :config
  (define-fringe-bitmap 'git-gutter-fr:added [224] nil nil '(center repeated))
  (define-fringe-bitmap 'git-gutter-fr:modified [224] nil nil '(center repeated))
  (define-fringe-bitmap 'git-gutter-fr:deleted [128 192 224 240] nil nil 'bottom))