Doom Emacs configuration

Table of Contents

1. Table of Contents   TOC_2_org

2. Overview

2.1. Introduction

Welcome to my improved Doom Emacs configuration.

This is my actual live configuration. It’s written using the literate programming concept in mind, using my personalized Doom Emacs editor with org mode.

This webpage has been exported from a single org file, with the help of the readtheorg theme. Using this approach, it costs me nearly zero effort to keep my config in sync with this documentation.

The code blocks in this document are eventually tangled to the /.doom.d/config.el file, and this file takes the documentation of it.

As this document is always a work in progress, you may find incomplete, non functional or commented config. The configuration is divided between 5 categories with subcategories underneath.

2.2. Dependencies

2.2.1. Emacs

Refer to the getting started guide, in order to install the dependencies that Doom Emacs requires.

Also, install fira code fonts. Mac homebrew instructions below.

brew install cask font-fira-code

2.2.2. Lang

A doom doctor run yields:

> Checking your enabled modules… > :lang org ! Couldn’t find the pngpaste executable. org-download-clipboard will not work. > :lang sh ! Couldn’t find shellcheck. Shell script linting will not work > :lang web ! Couldn’t find stylelint. Linting for CSS modes will not work. ! Couldn’t find js-beautify. Code formatting in JS/CSS/HTML modes will not work.

Install to fix.

brew install pngpaste shellcheck yarn

And install the following web-mode deps.

yarn global add stylelint stylelint-config-standard js-beautify

The doom doctor should be happy again!

2.3. Keybindings

As a personal cheatsheet, I’ve created a few tables of all the (extra) keybindings this configuration exposes, to make sure I remember and that I can reference them quickly when needed.

2.3.1. Dired

Keybindings related to dired.

Description Keybinding
Open dired spc d d
Open dired in dir of current buffer spc d j
Image previews inside dired (need to test) spc d p
Dired view file inside dired (need to test) spc d v

2.3.2. Registers

Keybindings related to accessing the Emacs register.

Description Keybinding
Copy to register spc r c
Frameset to register spc r f
Insert contents of register spc r i
Jump to register spc r j
List registers spc r l
Number to register spc r n
Interactively choose a register spc r r
View a register spc r v
Window configuration to register spc r w
Increment register spc r +
Point to register spc r SPC

2.3.3. Bookmarks

Keybindings related to bookmarks.

Description Keybinding
Open bookmarks list spc b h

2.3.4. Elisp

Keybindings related to executing elisp code.

Description Keybinding
Evaluate elisp in buffer spc e b
Evaluate defun spc e d
Evaluate elisp expression spc e e
Evaluate last sexpression spc e l
Evaluate elisp in region spc e r

2.4. Summary

The headings above contains documentation, and the code blocks underneath contain my actual Doom Emacs configuration.

3. Default settings

This heading contains some random configurable emacs settings.

3.1. Username and e-mail

Set your username and e-mail. Some functionality uses this to identify you, e.g. GPG configuration, email clients, file templates and snippets.

(setq user-full-name "hyperfocus"
      user-mail-address "email@email.com")

3.2. Notebook location

Store the location of my notes notebook for consumption in this config.

(defvar notes-directory "~/Library/CloudStorage/Dropbox/Notebooks/notebook/notes")
(defvar notebook-directory "~/Library/CloudStorage/Dropbox/Notebooks/notebook")

3.3. Default theme

Start Doom emacs with a light or dark theme, during the day I prefer the light time, during the night I prefer the dark theme.

(setq doom-theme 'doom-tomorrow-day)
;; (setq doom-theme 'doom-solarized-light)
;; (setq doom-theme 'doom-one-light)
;; (setq doom-theme 'doom-one)

3.4. Default shell

Change default shell used in Emacs to bash, due to the following message displayed by doom doctor.

Fish (and possibly other non-POSIX shells) is known to inject garbage output into some of the child processes that Emacs spawns. Many Emacs packages/utilities will choke on this output, causing unpredictable issues. To get around this, either:

  • Add the following to $DOOMDIR/config.el:

    (setq shell-file-name (executable-find “bash”))

  • Or change your default shell to a POSIX shell (like bash or zsh) and explicitly configure your terminal apps to use the shell you want.

Set default shell to bash

(setq shell-file-name (executable-find "bash"))

If you opt for option 1 and use one of Emacs’ terminal emulators, you will also need to configure them to use Fish, e.g.

(setq-default vterm-shell (executable-find “fish”))

(setq-default explicit-shell-file-name (executable-find “fish”))

Still use fish in other places

(setq-default vterm-shell (executable-find "fish"))
(setq-default explicit-shell-file-name (executable-find "fish"))

3.5. Font configuration

The font configuration, very important to properly configure to have a good experience.

Font configuration of my 16 inch Macbook, usually hooked up to a 27 inch 2560x1440 monitor.

(setq doom-font (font-spec :family "Fira Code" :size 18)
      doom-big-font-increment 6
      doom-variable-pitch-font (font-spec :family "Fira Code" :size 18))

3.6. Simple default settings

A few random default settings, most individual setting have been commented inline.

(setq-default delete-by-moving-to-trash t ; Delete files to trash
              window-combination-resize t ; Take new window space from all other windows (not just current)
              x-stretch-cursor t) ; Stretch cursor to the glyph width

Change a few variables.

(setq undo-limit 80000000 ; Raise undo-limit to 80Mb
      evil-want-fine-undo t ; By default while in insert all changes are one big blob. Be more granular
      truncate-string-ellipsis "…") ; Unicode ellispis are nicer than "...", and also save /precious/ space

Display battery percentage on laptops.

(unless (equal "Battery status not available"
               (battery))
  (display-battery-mode 1)) ; On laptops it's nice to know how much power you have
(global-subword-mode 1) ; Iterate through CamelCase words

Enable time in the mode-line.

(display-time-mode 1)

Easily escape after hitting the leader key using the ESC button. The :g flag stands for global.

(map! :leader :desc "Escape globally" :g "<escape>" #'keyboard-escape-quit)

3.7. Enable auto revert mode

Prevent files from getting outdated by changes outside of Emacs. Also read the related docs.

Global Auto-Revert Mode is a global minor mode that reverts any buffer associated with a file when the file changes on disk. Enable it.

(setq global-auto-revert-mode 1)

Also revert Dired and other non file buffers.

(setq global-auto-revert-non-file-buffers t)

3.8. Disable git modes

Turn off treemacs git mode, I’ll notice what has changed in magit anyways.

(after! treemacs
  (setq treemacs-git-mode nil)) ; nil, simple, extended, deferred

3.9. Start fullscreen

Start Doom Emacs in full screen.

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

3.10. Default browser

Set the default browser to open links to use Brave

(setq browse-url-browser-function 'browse-url-generic
      browse-url-generic-program "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser")

Set the default browser to open links in Microsoft Edge

;; (setq browse-url-browser-function 'browse-url-generic
;;       browse-url-generic-program "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge")

Set the default browser to open links to Google Chrome

;; (setq browse-url-browser-function 'browse-url-generic
;;       browse-url-generic-program "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")

3.11. File associations

Associate *.mdx files with markdown mode

;; Associate .mdx files with markdown-mode
(add-to-list 'auto-mode-alist '("\\.mdx\\'" . markdown-mode))

4. Improvements

This heading contains some improvements to the default Doom Emacs experience.

4.1. Dired improvements

A few extra dired keybindings (credits go to distrotubes config).

(map! :leader
      :desc "Dired"
      "d d" #'dired
      :leader
      :desc "Dired jump to current"
      "d j" #'dired-jump
      (:after dired
        (:map dired-mode-map
         :leader
         :desc "Peep-dired image previews"
         "d p" #'peep-dired
         :leader
         :desc "Dired view file"
         "d v" #'dired-view-file)))

Make ’h’ and ’l’ go back and forward in dired. Much faster to navigate the directory structure!

(evil-define-key 'normal dired-mode-map
  (kbd "h") 'dired-up-directory
  (kbd "l") 'dired-open-file) ; use dired-find-file instead if not using dired-open package

If peep-dired is enabled, you will get image previews as you go up/down with ’j’ and ’k’.

(evil-define-key 'normal peep-dired-mode-map
  (kbd "j") 'peep-dired-next-file
  (kbd "k") 'peep-dired-prev-file)
(add-hook 'peep-dired-hook 'evil-normalize-keymaps)

Get file icons in dired.

(add-hook 'dired-mode-hook 'all-the-icons-dired-mode)

With dired-open plugin, you can launch external programs for certain extensions. For example, I set all .png files to open in ’sxiv’ and all .mp4 files to open in ’mpv’.

(setq dired-open-extensions '(("gif" . "sxiv")
                              ("jpg" . "sxiv")
                              ("png" . "sxiv")
                              ("mkv" . "mpv")
                              ("mp4" . "mpv")))

4.2. Projectile improvements

Improvements inspired from projectile docs.

Also add a Makefile as a project root indicator.

(after! projectile
  (add-to-list 'projectile-project-root-files "Makefile"))

4.3. Doom modeline adjustments

Don’t display icons for buffer states. It respects doom-modeline-icon.

(setq doom-modeline-buffer-state-icon nil)

Don’t display buffer modification icon. It respects doom-modeline-icon and doom-modeline-buffer-state-icon.

(setq doom-modeline-buffer-modification-icon nil)

Customize a few theme variables to make sure red coloured unsaved buffers are not visible in the modeline, as I’m using super-save package anyways.

(custom-set-faces
 '(doom-modeline-buffer-modified ((t (:inherit (bold bold)))))
 '(doom-modeline-buffer-path ((t (:inherit (bold bold)))))
 '(doom-modeline-evil-normal-state ((t (:inherit bold))))
 '(doom-modeline-evil-insert-state ((t (:inherit (bold bold)))))
 '(doom-modeline-project-dir ((t (:inherit (bold bold)))))
 '(doom-modeline-highlight ((t (:inherit mode-line-emphasis))))
 '(doom-modeline-input-method ((t (:inherit (mode-line-emphasis bold)))))
 '(doom-modeline-project-root-dir ((t (:inherit (mode-line-emphasis bold))))))

4.4. Suppress messages in minibuffer

Suppress “Beginning of buffer” and “End of buffer” messages, as I find them useless.

(defadvice evil-previous-line (around silencer activate)
  (condition-case nil
    ad-do-it
    ((beginning-of-buffer))))

(defadvice evil-next-line (around silencer activate)
  (condition-case nil
    ad-do-it
    ((end-of-buffer))))

Suppress “Beginning of line” and “End of line” messages, as they’re also useless.

(defadvice evil-backward-char (around silencer activate)
  (condition-case nil
    ad-do-it
    ((beginning-of-line))))

(defadvice evil-forward-char (around silencer activate)
  (condition-case nil
    ad-do-it
    ((end-of-line))))

4.5. Magit auto save

Save all buffers before invoking any magit actions, as an alternative to broken magit-status super save trigger (which doesn’t work anymore).

(after! magit
  (setq magit-save-repository-buffers 'dontask))

4.6. Scrolling

Scroll one line at a time (less “jumpy” than defaults), taken from Emacs docs.

(setq mouse-wheel-scroll-amount '(1 ((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

4.7. Open treemacs on startup

After starting Doom it’s the first thing I do, so why not do it automatically each time.

(add-hook 'window-setup-hook #'+treemacs/toggle)

4.8. Mac change option to control

To change the interpretation of Mac keys from within Emacs, you can use several variables, as described on this StackExchange post.

Change right command to control in text-mode (during multiple modes).

(add-hook 'text-mode-hook (lambda () (when (eq system-type 'darwin) (setq-local mac-right-command-modifier 'control))))
(add-hook 'magit-log-select-mode-hook (lambda () (when (eq system-type 'darwin) (setq-local mac-right-command-modifier 'control))))
(add-hook 'dired-mode-hook (lambda () (when (eq system-type 'darwin) (setq-local mac-right-command-modifier 'control))))
(add-hook 'git-rebase-mode-hook (lambda () (when (eq system-type 'darwin) (setq-local mac-right-command-modifier 'control))))

Bring back the the command modifier to left as some modes behave like an exception (somehow).

(add-hook 'gfm-mode-hook (lambda () (when (eq system-type 'darwin) (setq-local mac-right-command-modifier 'left))))
(add-hook 'yaml-mode-hook (lambda () (when (eq system-type 'darwin) (setq-local mac-right-command-modifier 'left))))

4.9. Display ANSI color codes

Inspired from this SO post.

(use-package! ansi-color
  :defer t ;; loads the package when needed
  :config
  (defun display-ansi-colors ()
    (interactive)
    (ansi-color-apply-on-region (point-min) (point-max))))

4.10. Open currently open file in VSCode

Helper function to open currently open file in VSCode (currently changed to VSCode AI editor fork called ’Cursor’).

(defun open-current-file-in-vscode ()
  "Open the current buffer's file in Visual Studio Code."
  (interactive)
  (let ((file-path (buffer-file-name)))
    (if file-path
        (call-process "cursor" nil 0 nil file-path)
      (error "Current buffer is not visiting a file."))))

Keybinding to open currently open file in VSCode.

(map! :leader
      :desc "Open in VSCode" "ov" #'open-current-file-in-vscode)

4.11. Open treemacs after opening project

Automatically open treemacs after opening new project.

(add-hook 'projectile-after-switch-project-hook #'+treemacs/toggle)

5. Org mode

This heading contains settings related to improving the org mode experience.

5.1. Default org folder

If you use org and don’t want your org files in the default location below, change ’org-directory’. It must be set before org loads!

(setq org-directory notebook-directory)

5.2. Create doc skeleton

Let’s define a skeleton with a title, author, description and tags ready to pop-out where we need it to at the beginning of a new org file.

(define-skeleton generate-new-header-org
  "Prompt for title, description and tags"
  nil
  '(setq title  (skeleton-read "Title: "))
  '(setq description  (skeleton-read "Description: "))
  '(setq tags (skeleton-read "tags: "))
  "#+TITLE: " title \n
  "#+AUTHOR: Hyper Focus" \n
  "#+DESCRIPTION: " description \n
  "#+TAGS: " tags \n
  )

5.3. Configure agenda

Setup org agenda folder with multiple org.

(after! org
  (setq org-agenda-files '(notes-directory
                           notebook-directory)))

Configure custom agenda views, inspired from Emacs From Scratch #6.

(after! org
  (setq org-agenda-custom-commands
        '(("d" "Dashboard"
           ((agenda "" ((org-deadline-warning-days 7)))
            (todo "NEXT"
                  ((org-agenda-overriding-header "Next Tasks")))
            (tags-todo "agenda/ACTIVE" ((org-agenda-overriding-header "Active Projects")))))

          ("n" "Next Tasks"
           ((todo "NEXT"
                  ((org-agenda-overriding-header "Next Tasks")))))

          ("W" "Work Tasks" tags-todo "+work")

          ;; Low-effort next actions
          ("e" tags-todo "+TODO=\"NEXT\"+Effort<15&+Effort>0"
           ((org-agenda-overriding-header "Low Effort Tasks")
            (org-agenda-max-todos 20)
            (org-agenda-files org-agenda-files)))

          ("w" "Workflow Status"
           ((todo "WAIT"
                  ((org-agenda-overriding-header "Waiting on External")
                   (org-agenda-files org-agenda-files)))
            (todo "REVIEW"
                  ((org-agenda-overriding-header "In Review")
                   (org-agenda-files org-agenda-files)))
            (todo "PLAN"
                  ((org-agenda-overriding-header "In Planning")
                   (org-agenda-todo-list-sublevels nil)
                   (org-agenda-files org-agenda-files)))
            (todo "BACKLOG"
                  ((org-agenda-overriding-header "Project Backlog")
                   (org-agenda-todo-list-sublevels nil)
                   (org-agenda-files org-agenda-files)))
            (todo "READY"
                  ((org-agenda-overriding-header "Ready for Work")
                   (org-agenda-files org-agenda-files)))
            (todo "ACTIVE"
                  ((org-agenda-overriding-header "Active Projects")
                   (org-agenda-files org-agenda-files)))
            (todo "COMPLETED"
                  ((org-agenda-overriding-header "Completed Projects")
                   (org-agenda-files org-agenda-files)))
            (todo "CANC"
                  ((org-agenda-overriding-header "Cancelled Projects")
                   (org-agenda-files org-agenda-files))))))))

5.4. Org variables

A few random variables to improve the org experience.

Add timestamp when task moves to DONE state.

(setq org-log-done 'time)

Fold org log entries into drawer.

(setq org-log-into-drawer t)

5.5. Org to do keywords

Change the to do keywords to something more useful. Also search for the variable in modules/lang/org/config.el to see the way doom sets it up by default. Anything before the | character are active states, and anything after | are completed states.

(after! org
  (setq org-todo-keywords
        '((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!)")
          (sequence "BACKLOG(b)" "PLAN(p)" "READY(r)" "ACTIVE(a)" "REVIEW(v)" "WAIT(w@/!)" "HOLD(h)" "|" "COMPLETED(c)" "CANC(k@)"))))

5.6. Capture templates

Custom capture template, overwriting the default Doom Emacs workflow as defined in modules/lang/org/config.el.

(after! org
  (setq org-capture-templates
        '(("t" "Tasks / Projects")
          ("tt" "Task" entry (file+olp (concat my-notes-directory "/TODO.org") "Inbox")
           "* TODO %?\n  %U\n  %a\n  %i" :empty-lines 1))))

5.7. Prettier headings

Prettier org icons.

Nicer looking ellipsis when collapsing a heading.

(after! org
  (setq org-ellipsis " ▾"))

A few nicer looking org bullet points.

(use-package! org-bullets
  :after org
  :hook (org-mode . org-bullets-mode)
  :custom
  (org-bullets-bullet-list '("◉" "○" "●" "○" "●" "○" "●")))

5.8. Org pandoc import

Enable org-pandoc-import package.

(use-package! org-pandoc-import :after org)

5.9. Org tree slide

Initialize the usage of org-tree-slide.

(use-package! org-tree-slide
  :hook ((org-tree-slide-play . efs/presentation-setup)
         (org-tree-slide-stop . efs/presentation-end))
  :commands org-tree-slide-mode
  :config
  (setq org-image-actual-width nil)
  (setq org-tree-slide-header nil)
  (when (modulep! :editor evil)
    (map! :map org-tree-slide-mode-map
          :n [C-right] #'org-tree-slide-move-next-tree
          :n [C-left]  #'org-tree-slide-move-previous-tree)
    (add-hook 'org-tree-slide-mode-hook #'evil-normalize-keymaps)))

Let’s add the hook code when org-tree-slide-mode starts

(defun efs/presentation-setup ()
  ;; Hide the mode line
  (hide-mode-line-mode 1)

  ;; Enable the simple profile
  (org-tree-slide-simple-profile)

  ;; Display images inline
  (org-display-inline-images) ;; Can also use org-startup-with-inline-images

  ;; Scale the text. The next line is for basic scaling:
  (setq text-scale-mode-amount 2.5)
  (text-scale-mode 1))

  ;; This option is more advanced, allows you to scale other faces too
  ;; (setq-local face-remapping-alist '((default (:height 2.0) variable-pitch)
  ;;                                   (org-verbatim (:height 1.75) org-verbatim)
  ;;                                   (org-block (:height 1.25) org-block))))

Let’s add the hook code when org-tree-slide-mode stops

(defun efs/presentation-end ()
  ;; Show the mode line again
  (hide-mode-line-mode 0)

  ;; Turn off text scale mode (or use the next line if you didn't use text-scale-mode)
  (text-scale-mode 0))

  ;; If you use face-remapping-alist, this clears the scaling:
  ;; (setq-local face-remapping-alist '((default variable-pitch default))))

6. Keybindings

This heading contains configuration to create some extra keybindings for convenience.

6.1. Workspace switcher

This bit of code allows me to keep switching between workspaces, even though I’m in the treemacs buffer.

(define-key! evil-treemacs-state-map
    "] w" #'+workspace/switch-right
    "[ w" #'+workspace/switch-left)

6.2. Arrow window navigation

Use the arrows to move around. I actually mostly use leader + hjkl to move between windows, but I figured why not just keep this config, in case I use it from time to time.

(after! evil
  (map! :map evil-window-map
        (:leader
         (:prefix ("w" . "Select Window")
          :n :desc "Left"  "<left>" 'evil-window-left
          :n :desc "Up"    "<up>" 'evil-window-up
          :n :desc "Down"  "<down>" 'evil-window-down
          :n :desc "Right" "<right>" 'evil-window-right
          ))
        ))

6.3. Chezmoi

Add Chezmoi keybindings.

(map! :leader
      (:prefix-map ("o c" . "Chezmoi")
       :desc "Chezmoi sync files" "s" #'chezmoi-sync-files
       :desc "Chezmoi diff all" "d" #'chezmoi-diff
       :desc "Chezmoi find source file" "f" #'chezmoi-find
       :desc "Chezmoi open buffer target file" "o" #'chezmoi-open-other))

6.4. Registers

A few shortcuts to register functions for easier access (credits go to distrotubes config.org).

(map! :leader
      :desc "Copy to register"
      "r c" #'copy-to-register
      :leader
      :desc "Frameset to register"
      "r f" #'frameset-to-register
      :leader
      :desc "Insert contents of register"
      "r i" #'insert-register
      :leader
      :desc "Jump to register"
      "r j" #'jump-to-register
      :leader
      :desc "List registers"
      "r l" #'list-registers
      :leader
      :desc "Number to register"
      "r n" #'number-to-register
      :leader
      :desc "Interactively choose a register"
      "r r" #'counsel-register
      :leader
      :desc "View a register"
      "r v" #'view-register
      :leader
      :desc "Window configuration to register"
      "r w" #'window-configuration-to-register
      :leader
      :desc "Increment register"
      "r +" #'increment-register
      :leader
      :desc "Point to register"
      "r SPC" #'point-to-register)

6.5. Bookmarks

Keybinding to list all bookmarks (spc b h).

(map! :leader
      :desc "Bookmark list" "bh" #'list-bookmarks)

6.6. Evaluate elisp

Better keybindings for evaluating elisp functions (inspired from distrotube).

(map! :leader
      :desc "Evaluate elisp in buffer"
      "e b" #'eval-buffer
      :leader
      :desc "Evaluate defun"
      "e d" #'eval-defun
      :leader
      :desc "Evaluate elisp expression"
      "e e" #'eval-expression
      :leader
      :desc "Evaluate last sexpression"
      "e l" #'eval-last-sexp
      :leader
      :desc "Evaluate elisp in region"
      "e r" #'eval-region)

6.7. Navigate between buffers

Navigate between buffers using ⌃ (⇧) tab

(map! :nvi "C-<tab>" #'next-buffer)
(map! :nvi "C-S-<iso-lefttab>" #'previous-buffer)

Navigate between buffers using ⇧ ← / →

(map! :nvi "S-<right>" #'next-buffer)
(map! :nvi "S-<left>" #'previous-buffer)

Navigate between buffers using ⌘ ⇧ [ / ]

(map! :nvi "s-}" #'next-buffer)
(map! :nvi "s-{" #'previous-buffer)

Navigate between buffers using ⌥ ⌘ ← / →

(map! :nvi "M-s-<right>" #'next-buffer)
(map! :nvi "M-s-<left>" #'previous-buffer)

6.8. Navigate within file

Go up and down within a file using ⌘ ↑ / ↓

(map! :nvi "s-<up>" #'evil-goto-first-line)
(map! :nvi "s-<down>" #'evil-goto-line)

6.9. Swap workspace left and right

Swap current workspace to left and right.

(map! :nvi "s-<" #'+workspace/swap-left)
(map! :nvi "s->" #'+workspace/swap-right)

6.10. Focus on treemacs

Focus cursor on treemacs.

(map! :nvi "s-e" #'treemacs-select-window)

6.11. Fix jump between treemacs and back

Override default evil-window-next capability.

(map! :leader
      :desc "Fix select treemacs window"
      "w w" #'treemacs-select-window)

7. Utilities

This heading contains configuration of Emacs utilities that improve the experience.

7.1. Super save

Super-save is a super useful package that automatically saves buffers whenever any state events happen.

Enable super save mode.

(use-package! super-save
  :ensure t
  :config
  (super-save-mode +1))

Save all open buffers (useful in the case of making simultaneous edits with grep).

(setq super-save-all-buffers t)

Add many super save triggers, so the buffer saves during any action when you’ve stopped editing the buffer.

(add-to-list 'super-save-triggers 'evil-window-next)
(add-to-list 'super-save-triggers 'evil-window-prev)
(add-to-list 'super-save-triggers 'next-buffer)
(add-to-list 'super-save-triggers 'previous-buffer)
(add-to-list 'super-save-triggers 'switch-to-buffer)
(add-to-list 'super-save-triggers 'other-window )
(add-to-list 'super-save-triggers 'treemacs-select-window)
(add-to-list 'super-save-triggers 'windmove-up)
(add-to-list 'super-save-triggers 'windmove-down)
(add-to-list 'super-save-triggers 'windmove-left)
(add-to-list 'super-save-triggers 'windmove-right)
(add-to-list 'super-save-triggers '+workspace/switch-left)
(add-to-list 'super-save-triggers '+workspace/switch-right)
(add-to-list 'super-save-triggers '+workspace/switch-to-final)
(add-to-list 'super-save-triggers '+workspace/switch-to)
(add-to-list 'super-save-triggers '+ivy/projectile-find-file)

Suppress messages in the Messages buffer and echo area.

(setq super-save-silent t)

Enable deleting trailing white spaces before saving (except for the current line).

(setq super-save-delete-trailing-whitespace 'except-current-line)

7.2. Magit forge

Magit forge allows you to manage git issues and other project related settings from within Magit.

Enable magit forge package.

(use-package! forge)

Changed the default auth-sources variable to only include .authinfo as source, to fix an issue.

(setq auth-sources '("~/.authinfo"))

Show 100 open topics and never show any closed topics, for both issues and pull requests.

(setq forge-topic-list-limit '(100 . 0))

7.3. Web mode

Web mode is an emacs major mode for editing web templates.

Enable JSX syntax highlighting in .js/.jsx files.

(setq web-mode-content-types-alist '(("jsx" . "\\.js[x]?\\'")))

7.4. Denote

Adding notes with denote.

(use-package! denote
  :custom
  (denote-directory (expand-file-name notes-directory))
  (denote-infer-keywords nil)
  (denote-history-completion-in-prompts nil)
  (denote-file-type 'markdown-yaml) ; ('markdown-yaml' or 'org')
  (denote-save-buffers t)
  :hook
  (dired-mode . denote-dired-mode))

Configure consult-denote package.

(use-package! consult-denote
  :after (denote consult)
  :ensure t
  :config
  ;; (setq consult-denote-grep-command "consult-grep") ;; it can't find "consult-ripgrep" for some reason
  (consult-denote-mode 1))

Configure denote keybindings.

(map! :leader
      (:prefix-map ("n d" . "Denote")
       :desc "Denote create file" "c" #'denote
       :desc "Denote drawer" "d" #'denote-open-or-create
       :desc "Denote find" "f" #'consult-denote-find
       :desc "Denote link" "l" #'denote-link
       :desc "Denote rename file" "R" #'denote-rename-file
       ;; :desc "Denote grep" "g" #'consult-denote-grep ; does not yet work
       :desc "Denote show backlink" "b" #'denote-backlinks
       :desc "Denote insert dblock" "i b" #'denote-org-dblock-insert-links))

Additional denote keybindings.

(map! :leader
      (:prefix-map ("o n" . "notes")
       :desc "Denote drawer" "" #'denote-open-or-create))

7.5. Consult notes

Configure consult-notes package.

(use-package! consult-notes
  :commands (consult-notes
             consult-notes-search-in-all-notes)
  :config
  (setq consult-notes-file-dir-sources '(("Notes" ?n notes-directory))) ;; ?n is a narrowing key character
  (when (locate-library "denote")
    (consult-notes-denote-mode))
  (setq consult-notes-denote-files-function (function denote-directory-files)))

7.6. Obsidian

Add function to open markdown files in obsidian app.

(map! :map doom-leader-map "n r o" (λ! (let* ((full-path (buffer-file-name))
                                              (pattern "/notes/\\(.*?\\)\\.md$")
                                              (vault-name "notebook")
                                              obsidian-uri match)
                                              (when (and full-path (string-match pattern full-path))
                                                (setq match (match-string 1 full-path))
                                                (setq obsidian-uri (format "obsidian://open?vault=%s&file=%s" vault-name match))
                                              (start-process "open-obsidian" nil "open" obsidian-uri)))))

7.7. Ebooks

Use Calibre from within Emacs with CalibreDB. Took inspiration from Tecosaurs config.

Enable package and some keybindings.

(use-package! calibredb
  :commands calibredb
  :config
  (setq calibredb-root-dir "~/Dropbox/Ebooks"
        calibredb-db-dir (expand-file-name "metadata.db" calibredb-root-dir))
  (map! :map calibredb-show-mode-map
        :ne "?" #'calibredb-entry-dispatch
        :ne "o" #'calibredb-find-file
        :ne "O" #'calibredb-find-file-other-frame
        :ne "V" #'calibredb-open-file-with-default-tool
        :ne "s" #'calibredb-set-metadata-dispatch
        :ne "e" #'calibredb-export-dispatch
        :ne "q" #'calibredb-entry-quit
        :ne "." #'calibredb-open-dired
        :ne [tab] #'calibredb-toggle-view-at-point
        :ne "M-t" #'calibredb-set-metadata--tags
        :ne "M-a" #'calibredb-set-metadata--author_sort
        :ne "M-A" #'calibredb-set-metadata--authors
        :ne "M-T" #'calibredb-set-metadata--title
        :ne "M-c" #'calibredb-set-metadata--comments)
  (map! :map calibredb-search-mode-map
        :ne [mouse-3] #'calibredb-search-mouse
        :ne "RET" #'calibredb-find-file
        :ne "?" #'calibredb-dispatch
        :ne "a" #'calibredb-add
        :ne "A" #'calibredb-add-dir
        :ne "c" #'calibredb-clone
        :ne "d" #'calibredb-remove
        :ne "D" #'calibredb-remove-marked-items
        :ne "j" #'calibredb-next-entry
        :ne "k" #'calibredb-previous-entry
        :ne "l" #'calibredb-virtual-library-list
        :ne "L" #'calibredb-library-list
        :ne "n" #'calibredb-virtual-library-next
        :ne "N" #'calibredb-library-next
        :ne "p" #'calibredb-virtual-library-previous
        :ne "P" #'calibredb-library-previous
        :ne "s" #'calibredb-set-metadata-dispatch
        :ne "S" #'calibredb-switch-library
        :ne "o" #'calibredb-find-file
        :ne "O" #'calibredb-find-file-other-frame
        :ne "v" #'calibredb-view
        :ne "V" #'calibredb-open-file-with-default-tool
        :ne "." #'calibredb-open-dired
        :ne "b" #'calibredb-catalog-bib-dispatch
        :ne "e" #'calibredb-export-dispatch
        :ne "r" #'calibredb-search-refresh-and-clear-filter
        :ne "R" #'calibredb-search-clear-filter
        :ne "q" #'calibredb-search-quit
        :ne "m" #'calibredb-mark-and-forward
        :ne "f" #'calibredb-toggle-favorite-at-point
        :ne "x" #'calibredb-toggle-archive-at-point
        :ne "h" #'calibredb-toggle-highlight-at-point
        :ne "u" #'calibredb-unmark-and-forward
        :ne "i" #'calibredb-edit-annotation
        :ne "DEL" #'calibredb-unmark-and-backward
        :ne [backtab] #'calibredb-toggle-view
        :ne [tab] #'calibredb-toggle-view-at-point
        :ne "M-n" #'calibredb-show-next-entry
        :ne "M-p" #'calibredb-show-previous-entry
        :ne "/" #'calibredb-search-live-filter
        :ne "M-t" #'calibredb-set-metadata--tags
        :ne "M-a" #'calibredb-set-metadata--author_sort
        :ne "M-A" #'calibredb-set-metadata--authors
        :ne "M-T" #'calibredb-set-metadata--title
        :ne "M-c" #'calibredb-set-metadata--comments))

Use nov to read the epub files.

(use-package! nov
  :mode ("\\.epub\\'" . nov-mode)
  :config
  (map! :map nov-mode-map
        :n "RET" #'nov-scroll-up)

  (defun doom-modeline-segment--nov-info ()
    (concat
     " "
     (propertize
      (cdr (assoc 'creator nov-metadata))
      'face 'doom-modeline-project-parent-dir)
     " "
     (cdr (assoc 'title nov-metadata))
     " "
     (propertize
      (format "%d/%d"
              (1+ nov-documents-index)
              (length nov-documents))
      'face 'doom-modeline-info)))

  (advice-add 'nov-render-title :override #'ignore)

  (defun +nov-mode-setup ()
    (face-remap-add-relative 'variable-pitch
                             :family "Merriweather"
                             :height 1.4
                             :width 'semi-expanded)
    (face-remap-add-relative 'default :height 1.3)
    (setq-local line-spacing 0.2
                next-screen-context-lines 4
                shr-use-colors nil)
    (require 'visual-fill-column nil t)
    (setq-local visual-fill-column-center-text t
                visual-fill-column-width 80
                nov-text-width 100)
    (visual-fill-column-mode 1)
    (hl-line-mode -1)

    (add-to-list '+lookup-definition-functions #'+lookup/dictionary-definition)

    (setq-local mode-line-format
                `((:eval
                   (doom-modeline-segment--workspace-name))
                  (:eval
                   (doom-modeline-segment--window-number))
                  (:eval
                   (doom-modeline-segment--nov-info))
                  ,(propertize
                    " %P "
                    'face 'doom-modeline-buffer-minor-mode)
                  ,(propertize
                    " "
                    'face (if (doom-modeline--active) 'mode-line 'mode-line-inactive)
                    'display `((space
                                :align-to
                                (- (+ right right-fringe right-margin)
                                   ,(* (let ((width (doom-modeline--font-width)))
                                         (or (and (= width 1) 1)
                                             (/ width (frame-char-width) 1.0)))
                                       (string-width
                                        (format-mode-line (cons "" '(:eval (doom-modeline-segment--major-mode))))))))))
                  (:eval (doom-modeline-segment--major-mode)))))

  (add-hook 'nov-mode-hook #'+nov-mode-setup))

7.8. Terraform LSP mode

Enable terraform-ls language server.

(after! lsp-mode
  (lsp-register-client
   (make-lsp-client :new-connection (lsp-stdio-connection '("/opt/homebrew/bin/terraform-ls" "serve"))
                    :major-modes '(terraform-mode)
                    :server-id 'terraform-ls))

  (add-hook 'terraform-mode-hook #'lsp))

Only show lsp-ui-doc popup with mouse

(setq lsp-ui-doc-enable t)
(setq lsp-ui-doc-show-with-cursor nil)
(setq lsp-ui-doc-show-with-mouse t)

Turn off the echo area documentation.

(setq lsp-eldoc-enable-hover nil)

Disable sideline diagnostics.

(setq lsp-ui-sideline-enable nil)

8. Troubleshooting

8.1. Tramp verbosity

Set Tramp verbosity

(setq tramp-verbose 6)

8.2. Org mode bug

Turn off cache to fix the following bug.

Warning (emacs): org-element–cache: Unregistered buffer modifications detected. Resetting. If this warning appears regularly, please report it to Org mode mailing list (M-x org-submit-bug-report). The buffer is: course.org Current command: nil Backtrace: “ backtrace-to-string(nil) org-element–cache-sync(#<buffer course.org>) apply(org-element–cache-sync #<buffer course.org>) timer-event-handler([t 0 0 599999 nil org-element–cache-sync (#<buffer course.org>) idle 999999]) ”

Turn back to t to see whether it’s fixed by now.

(setq org-element-use-cache t)

Author: Hyper Focus

Created: 2024-10-09 Wed 09:58