SophieBosio/.emacs.d

Sophie's Emacs configuration

Emacs Lisp

108

870 commits

updated Aug 30, 2026

See the code

README


#+TITLE: Sophie's Emacs Configuration
#+AUTHOR: Sophie Bosio
:PROPERTIES:
#+PROPERTY: header-args :tangle yes
#+OPTIONS: toc:2
#+STARTUP: overview
:END:

[[./images/config-screenshot.png]]

* About

This is my attempt at keeping my Emacs configuration organised and readable.

I write all my initialisation code in this document as code blocks and then use
=org-babel-tangle= to extract those code blocks into a separate file. That new,
generated file becomes my =init.el=. This way, I can document my code and explain
my choices to my future self - and to anyone else who might be interested in looking at it. I've stolen the code for doing this and several other tidbits from [[https://github.com/larstvei/dot-emacs/blob/master/init.org][Lars Tveito]].

If you're interested in this approach to writing and sharing your config, it's
called a "literate configuration" and there are lots of great blog posts out
there with inspiration and tips!

I've lifted a lot of code from other people's configurations, including:
- [[https://github.com/larstvei/dot-emacs/blob/master/init.org][Lars Tveito]]
- [[https://github.com/jakebox/jake-emacs][Jacob Boxerman]]
- [[https://github.com/purcell/emacs.d][Steve Purcell]]
- [[https://github.com/rougier][Nicolas Rougier]] (especially his Org setup)

I can heartily recommend checking those out.

You likely do not want to copy my configuration file, since it's full of idiosyncrasies and pretty subjective choices. But I do encourage you to take any bits and pieces that seem interesting, try them out, and incorporate the ones you like into your own config.

* Table of Contents :toc_2_gh:
- [[#about][About]]
- [[#setup][Setup]]
  - [[#prerequisites][Prerequisites]]
  - [[#initel-code][=init.el= Code]]
  - [[#git-tracking--practicalities][Git Tracking & Practicalities]]
  - [[#lexical-binding][Lexical Binding]]
  - [[#tangling][Tangling]]
- [[#start-up][Start-Up]]
  - [[#early-init][Early Init]]
  - [[#garbage-collection][Garbage Collection]]
  - [[#optimisations][Optimisations]]
  - [[#package-manager--package-sources][Package Manager & Package Sources]]
- [[#housekeeping][Housekeeping]]
  - [[#built-in-options][Built-in Options]]
  - [[#gpg-keys][GPG Keys]]
  - [[#local-files][Local Files]]
  - [[#terminal-setup][Terminal Setup]]
- [[#custom-keybindings][Custom Keybindings]]
  - [[#custom-keymap][Custom Keymap]]
  - [[#mac-os-modifier-keys][Mac OS Modifier Keys]]
  - [[#unbind-some-default-keys][Unbind Some Default Keys]]
- [[#visuals][Visuals]]
  - [[#decluttering][Decluttering]]
  - [[#frames--windows][Frames & Windows]]
  - [[#programming-specific-visuals][Programming-Specific Visuals]]
  - [[#fonts][Fonts]]
  - [[#icons--emojis][Icons & Emojis]]
  - [[#themes][Themes]]
  - [[#mode-line][Mode Line]]
  - [[#text-display-modes][Text Display Modes]]
- [[#general-editing][General Editing]]
  - [[#built-in-options-1][Built-In Options]]
  - [[#scrolling][Scrolling]]
  - [[#tabs--indentation][Tabs & Indentation]]
  - [[#kill-ring][Kill Ring]]
  - [[#auto-saving][Auto-Saving]]
  - [[#move-where-i-mean][Move Where I Mean]]
  - [[#text-editing-functions][Text Editing Functions]]
  - [[#crux][CRUX]]
  - [[#make-scripts-executable][Make Scripts Executable]]
- [[#buffers--navigation][Buffers & Navigation]]
  - [[#killing-buffers][Killing Buffers]]
  - [[#splitting-windows][Splitting Windows]]
  - [[#opening-closing--switching-windows][Opening, Closing, & Switching Windows]]
  - [[#projectile][Projectile]]
  - [[#javelin][Javelin]]
  - [[#ibuffer][IBuffer]]
  - [[#dired][Dired]]
- [[#completion][Completion]]
  - [[#vertico][Vertico]]
  - [[#vertico-posframe][Vertico Posframe]]
  - [[#corfu][Corfu]]
  - [[#orderless][Orderless]]
- [[#search][Search]]
  - [[#search-utilities][Search Utilities]]
  - [[#consult][Consult]]
  - [[#imenu-list][Imenu List]]
  - [[#marginalia][Marginalia]]
- [[#misc-packages][Misc. Packages]]
  - [[#version-control-magit--friends][Version Control (Magit & Friends)]]
  - [[#trying-packages][Trying Packages]]
  - [[#snippets][Snippets]]
  - [[#better-help-buffers][Better Help Buffers]]
  - [[#jinx-spellchecker][Jinx Spellchecker]]
  - [[#latex][LaTeX]]
  - [[#pdf-tools][PDF Tools]]
  - [[#epubs][EPUBs]]
  - [[#editor-config][Editor Config]]
  - [[#browser-preference][Browser Preference]]
  - [[#elfeed][Elfeed]]
  - [[#config-profiling][Config Profiling]]
- [[#org][Org]]
  - [[#visuals-1][Visuals]]
  - [[#general-interaction][General Interaction]]
  - [[#agenda][Agenda]]
  - [[#tasks][Tasks]]
  - [[#babel][Babel]]
  - [[#roam][Roam]]
  - [[#hugo][Hugo]]
  - [[#org-present][Org Present]]
  - [[#org-conveniencies][Org Conveniencies]]
- [[#programming][Programming]]
  - [[#preferences--extras][Preferences & Extras]]
  - [[#flycheck][Flycheck]]
  - [[#eldoc][Eldoc]]
  - [[#xref][xref]]
  - [[#http-requests][HTTP Requests]]
  - [[#tree-sitter][Tree-Sitter]]
  - [[#structural-editing][Structural Editing]]
  - [[#lsp][LSP]]
  - [[#programming-languages][Programming Languages]]
- [[#activating-custom-keybindings][Activating Custom Keybindings]]
- [[#todos][TODOs]]

* Setup
** Prerequisites

You probably don't want to run this configuration as-is, since it's highly personal and very likely contains things you don't want in your Emacs.

However, if you do want to try it, or if you want to steal a chunk and something's not working right, this is the software that I have installed in addition to Emacs and that is present in this config, one way or another.

This doubles as a memo to myself for when I need to set up a new machine.

- [[https://www.mozilla.org/en-US/firefox/][Firefox Browser]]
- [[https://github.com/BurntSushi/ripgrep][ripgrep]] search utility
- [[https://github.com/ggreer/the_silver_searcher][ag (The Silver Searcher)]] search utility
- [[https://tree-sitter.github.io/tree-sitter/][Tree-Sitter]] parser generator & incremental parsing library
- [[https://github.com/AbiWord/enchant][Enchant]] spellchecker library & [[https://aspell.net/][GNU Aspell]]
- [[https://www.tug.org/interest.html#free][LaTeX]] type-setting system

Here are the programming languages and utils I set up. The configuration for other languages I have in here shouldn't break anything if you don't have the accompanying software.

- [[https://docs.haskellstack.org/en/stable/][Stack]], [[https://www.haskell.org/cabal/][Cabal]], and [[https://github.com/haskell/haskell-language-server][hsl]] for Haskell - all of which you can install via [[https://www.haskell.org/ghcup/][GHCup]]
- [[https://agda.readthedocs.io/][Agda]], for which you also need Haskell and the Haskell packages [[https://haskell-alex.readthedocs.io/en/][Alex]] and [[https://haskell-happy.readthedocs.io/en/][Happy]]
- [[https://clojure.org/guides/install_clojure][Clojure]], for which you also need a JDK and Clojure runtime, for example the [[https://clojure.org/guides/install_clojure][Clojure CLI tools]] or [[https://leiningen.org/][Leiningen]]

I use these fonts:

- [[https://github.com/weiweihuanghuang/fragment-mono][Fragment Mono]]
- [[https://fonts.google.com/specimen/Open+Sans][Open Sans]]
- [[https://www.nerdfonts.com/font-downloads][Roboto Mono (Nerd Font)]]
- Apple Emoji

The rest of what you need *should* be downloaded by this configuration file. If you try it and find anything missing from this list, please let me know!

** =init.el= Code

As mentioned, I use =org-babel-tangle= and this document, written in Org mode.

The code below extracts the elisp configuration code and creates/overwrites the
=~/.emacs.d/init.el= configuration file when the =.org=-file is saved.
Therefore, changes are only done in the =.org=-file, where writing longer
comments about how things work and why things are added is easier, and then the resulting =init.el=-file remains clean and without excessive comments.

This is what the =init.el= file should look like, prompting it to tangle the =init.org= file and replace itself with that code.

#+BEGIN_SRC emacs-lisp :tangle no
;; We need org in order to make use of the tangling functionality
(require 'org)
;; Open the org-mode configuration
(find-file (concat user-emacs-directory "init.org"))
;; Tangle the file
(org-babel-tangle)
;; Load the tangled file
(load-file (concat user-emacs-directory "init.el"))
;; Byte-compile it
(byte-compile-file (concat user-emacs-directory "init.el"))
#+END_SRC

** Git Tracking & Practicalities

Now we also don't need to track the generated =init.el= file on Git, since it is directly derived from =init.org=.

This code makes Git ignore changes to =init.el=:

#+BEGIN_SRC sh :tangle no
git update-index --assume-unchanged init.el
#+END_SRC

If you do want to start tracking the file again, you can use:

#+BEGIN_SRC sh :tangle no
git update-index --no-assume-unchanged init.el
#+END_SRC

** Lexical Binding

First, I want lexical scoping for the =init=-file, so I will add that to the top of the file.

#+BEGIN_SRC emacs-lisp
;;; -*- lexical-binding: t -*-
#+END_SRC

** Tangling

Now to tangling! The rest of the text and code in this section is lifted directly from Lars' configuration.

The =init.el= should (after the first run) mirror the source blocks in the =init.org=. We can use =C-c C-v t= to run =org-babel-tangle=, which extracts the code blocks from the current file into a source-specific file (in this case a =.el=-file).

To avoid doing this each time a change is made we can add a function to the =after-save-hook= ensuring to always tangle and byte-compile =.org=-document after changes.

#+BEGIN_SRC emacs-lisp
   (defun tangle-init ()
     "If the current buffer is init.org the code-blocks are
   tangled, and the tangled file is compiled."
     (when (equal (buffer-file-name)
                  (expand-file-name (concat user-emacs-directory "init.org")))
       ;; Avoid running hooks when tangling.
       (let ((prog-mode-hook nil))
         (org-babel-tangle)
         (byte-compile-file (concat user-emacs-directory "init.el")))))

   (add-hook 'after-save-hook 'tangle-init)
#+END_SRC

* Start-Up
** Early Init

Emacs 27 introduced =early-init.el=, which is like =init.el= but ran before that, and before the UI and packages are initialised. I've taken code snippets from other configs to put in my =early-init.el= and the blocks in this section tangle to =early-init.el= instead of =init.el=.

In particular, the code below is a combination of code from:
- [[https://github.com/ogdenwebb/snug-emacs/blob/main/early-init.el][ogdenwebb's snug-emacs]]
- [[https://github.com/mnewt/dotemacs/blob/master/early-init.el][mnewt's dotemacsl]]
- [[https://github.com/jamescherti/minimal-emacs.d/blob/main/early-init.el][jamescherti's minimal emacs]]

#+begin_src emacs-lisp :tangle early-init.el
;;; -*- lexical-binding: t -*-

;; Defer garbage collection
(setq gc-cons-percentage 0.6)

;; Change default max size for reading processes (like LSP mode)
(setq read-process-output-max (* 4 1024 1024)) ;; 4mb

(set-language-environment "UTF-8")

;; Set-language-environment sets default-input-method, which is unwanted.
(setq default-input-method nil)

;; Prefer loading newer compiled files
(setq load-prefer-newer t)

;; Prevent the glimpse of un-styled Emacs by disabling these UI elements early.
(setq default-frame-alist
      '((vertical-scroll-bars . nil)
        (menu-bar-lines       . 0)
        (tool-bar-lines       . 0)))

(setq default-frame-scroll-bars 'right)
(setq scroll-bar-mode nil)

;; Resizing the Emacs frame can be a terribly expensive part of changing the
;; font. By inhibiting this, we easily halve startup times with fonts that are
;; larger than the system default.
(setq frame-inhibit-implied-resize t
      frame-resize-pixelwise       t)

;; Font compacting can be very resource-intensive, especially when rendering
;; icon fonts on Windows. This will increase memory usage.
(setq inhibit-compacting-font-caches t)

;; Ignore X resources; its settings would be redundant with the other settings
;; in this file and can conflict with later config (particularly where the
;; cursor color is concerned).
(advice-add #'x-apply-session-resources :override #'ignore)

;; A second, case-insensitive pass over `auto-mode-alist' is time wasted.
;; No second pass of case-insensitive search over auto-mode-alist.
(setq auto-mode-case-fold nil)

;; Disable bidirectional text scanning for a modest performance boost.
(setq-default bidi-display-reordering  'left-to-right
              bidi-paragraph-direction 'left-to-right)

;; Give up some bidirectional functionality for slightly faster re-display.
(setq bidi-inhibit-bpa t)

;; In PGTK, this timeout introduces latency. Reducing it from the default 0.1
;; improves responsiveness of childframes and related packages.
(when (boundp 'pgtk-wait-for-event-timeout)
  (setq pgtk-wait-for-event-timeout 0.001))

;; Disable warnings from the legacy advice API. They aren't useful.
(setq ad-redefinition-action 'accept)

;; Font compacting can be very resource-intensive, especially when rendering
;; icon fonts on Windows. This will increase memory usage.
(setq inhibit-compacting-font-caches t)

;; Unset `file-name-handler-alist' too (temporarily). Every file opened and
;; loaded by Emacs will run through this list to check for a proper handler for
;; the file, but during startup, it won’t need any of them.
(defvar file-name-handler-alist-old file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook
          (lambda ()
            (setq file-name-handler-alist file-name-handler-alist-old)))

;; For LSP mode, use plists for deserialization
;; For more info, see https://emacs-lsp.github.io/lsp-mode/page/performance/#use-plists-for-deserialization
(setenv "LSP_USE_PLISTS" "true")

;; Remove "For information about GNU Emacs..." message at startup
(advice-add #'display-startup-echo-area-message :override #'ignore)

;; Suppress the vanilla startup screen completely. Even if disabled with
;; `inhibit-startup-screen', it would still initialize anyway.
(advice-add #'display-startup-screen :override #'ignore)

;; Shave seconds off startup time by starting the scratch buffer in
;; `fundamental-mode'
(setq initial-major-mode 'fundamental-mode
      initial-scratch-message nil)

;; Disable startup screens and messages
(setq inhibit-splash-screen t)
#+end_src

From [[https://emacsnotes.wordpress.com/2022/09/11/three-bonus-keys-c-i-c-m-and-c-for-your-gui-emacs-all-with-zero-headache/][this blog post]] on the blog Emacs Notes, I've also lifted this piece of code that goes into =early-init.el=. Normally, the three keys =C-i=, =C-m= and =C-[= are reserved in GUI Emacs to match expected behaviour in terminal Emacs, and terminal applications generally. They're bound to =TAB=, =RET= and =ESC=, respectively.

But we can unlock them by giving the keys explicit names in =early-init.el= and using the new, named keys in our config if we surround the keybinding with chevrons. So, below I've called =C-i= by the "name" =C-i=. But since it's a name and not a literal key, I need to write =<C-i>= when I want to use the key to define a binding.

#+begin_src emacs-lisp :tangle early-init.el
(add-hook
 'after-make-frame-functions
 (defun setup-blah-keys (frame)
   (with-selected-frame frame
     (when (display-graphic-p) ; don't remove this condition, if you want
                               ; terminal Emacs to be usable
       (define-key input-decode-map (kbd "C-i") [C-i])
       (define-key input-decode-map (kbd "C-[") [C-lsb]) ; left square bracket
       (define-key input-decode-map (kbd "C-m") [C-m])))))
#+end_src

** Garbage Collection

Famously, the Emacs garbage collector can impede startup times quite dramatically. Therefore, a common tweak is to disable the garbage collector during initialisation, and then resetting it afterwards. Luckily, there exists a package exactly for this purpose called the [[https://github.com/emacsmirror/gcmh][Garbage Collector Magic Hack]]-

#+BEGIN_SRC emacs-lisp
(use-package gcmh
  :config
  (gcmh-mode 1))
#+END_SRC

** Optimisations

I found a lot of goodies in [[https://emacsredux.com/blog/2026/04/07/stealing-from-the-best-emacs-configs/][this blog post]] by Bozhidar Batsov about common Emacs config settings.

This prevents Emacs from trying to process right-to-left text (e.g., Arabic).

#+begin_src emacs-lisp
(setq-default bidi-display-reordering 'left-to-right
              bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)
#+end_src

Skip fontification while I'm typing.

#+begin_src emacs-lisp
(setq redisplay-skip-fontification-on-input t)
#+end_src

I also get quite a lot of compilation warnings, especially from native compilation, but they are usually safe to ignore.

#+begin_src emacs-lisp
(setq native-comp-async-report-warnings-errors 'silent) ;; native-comp warning
(setq byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
#+end_src

Disable warnings about obsolete functions when compiling.

#+begin_src emacs-lisp
(eval-when-compile
  (dolist (sym '(cl-flet lisp-complete-symbol))
    (setplist sym (use-package-plist-delete
                   (symbol-plist sym) 'byte-obsolete-info))))
#+end_src

This is an optimisation borrowed from Doom Emacs' =core.el=.

#+begin_src emacs-lisp
(setq which-func-update-delay 1.0)
#+end_src

Prevent Emacs from freezing when updating ELPA.

#+begin_src emacs-lisp
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")
#+end_src

Make sure we're using the correct native compilation cache.

#+begin_src emacs-lisp
(setq native-comp-eln-load-path '("~/.emacs.d/eln-cache/"))
#+end_src

** Package Manager & Package Sources

To manage downloaded packages, Emacs comes with =package.el= installed. In
addition, I want to use =use-package=, so let's make sure we have those loaded.

#+begin_src emacs-lisp
(require 'package)
(require 'use-package)
(require 'use-package-ensure)
(setq use-package-always-ensure t)
#+end_src

Next, I'll set up my package sources. These are very common and well-maintained mirrors.

#+BEGIN_SRC emacs-lisp
(setq package-archives
      '(("GNU ELPA"     . "https://elpa.gnu.org/packages/")
        ("MELPA"        . "https://melpa.org/packages/")
        ("ORG"          . "https://orgmode.org/elpa/")
        ("MELPA Stable" . "https://stable.melpa.org/packages/")
        ("nongnu"       . "https://elpa.nongnu.org/nongnu/"))
      package-archive-priorities
      '(("GNU ELPA"     . 20)
        ("MELPA"        . 15)
        ("ORG"          . 10)
        ("MELPA Stable" . 5)
        ("nongnu"       . 0)))
(package-initialize)
#+END_SRC

Also, please upgrade even built-in packages.

#+begin_src emacs-lisp
(setq package-install-upgrade-built-in t)
#+end_src

* Housekeeping
** Built-in Options

First, let's set the Emacs user and default directories explicitly:

 #+BEGIN_SRC emacs-lisp
 (setq user-emacs-directory "~/.emacs.d/")
 (setq default-directory "~/")
 #+END_SRC

 Set UFT-8 as preferred coding system.

 #+BEGIN_SRC emacs-lisp
 (set-language-environment    "UTF-8")
 (setq locale-coding-system   'utf-8)
 (prefer-coding-system        'utf-8)
 (set-default-coding-systems  'utf-8)
 (set-terminal-coding-system  'utf-8)
 (set-keyboard-coding-system  'utf-8)
 (set-selection-coding-system 'utf-8)
 #+END_SRC

 Don't warn me when opening files unless over 50 MB.

 #+begin_src emacs-lisp
 (setq large-file-warning-threshold (* 50 1024 1024))
 #+end_src

 From [[https://github.com/jamescherti/minimal-emacs.d/blob/8d975e882026f1433086e6dcf86c1b9acaac61a1/init.el#L159-L173][minimal-emacs.d]], don't create tons of lockfiles or backup files.

 #+begin_src emacs-lisp
 (setq create-lockfiles nil)
 (setq make-backup-files nil)

 (setq backup-directory-alist
       `(("." . ,(expand-file-name "backup" user-emacs-directory))))
 (setq tramp-backup-directory-alist backup-directory-alist)

 (setq backup-by-copying-when-linked t)
 (setq backup-by-copying             t) ; Backup by copying rather renaming
 (setq delete-old-versions           t) ; Delete excess backup versions silently
 (setq version-control               t) ; Use version numbers for backup files
 (setq kept-new-versions             5)
 (setq kept-old-versions             5)
 #+end_src

** GPG Keys

I want to use the standard GPG keyring for Emacs as well.

#+BEGIN_SRC emacs-lisp
(setq package-gnupghome-dir "~/.gnupg")
#+END_SRC

** Local Files

I have a folder with extensions that have been downloaded manually. I'll add these to the =load-path= so Emacs knows where to look for them. My folder is called "local-lisp".

#+BEGIN_SRC emacs-lisp
(defvar local-lisp (concat user-emacs-directory "local-lisp/"))
(add-to-list 'load-path  local-lisp)
(let ((default-directory local-lisp))
  (normal-top-level-add-subdirs-to-load-path))

(defvar local-lisp-themes (concat user-emacs-directory "local-lisp/themes/"))
(add-to-list 'custom-theme-load-path local-lisp-themes)
#+END_SRC

I'll initialise some lists to an empty list. I'll add to the lists both in =private.el= and further down in this file, so it's important that the variable already exists.

#+begin_src emacs-lisp
(setq org-agenda-files '())
(setq register-alist '())
(setq projectile-project-search-path '())
#+end_src

And add custom settings from =custom.el= and private settings from =private.el= if they exist.

#+begin_src emacs-lisp
(setq private-file (expand-file-name "private.el" user-emacs-directory))
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

(when (file-exists-p private-file)
  (load-file private-file))

(when (file-exists-p custom-file)
       (load-file custom-file))
#+end_src

** Terminal Setup

Track current directory in shell.

#+begin_src emacs-lisp
(dirtrack-mode t)
#+end_src

*** Mac OS Environment Variables

On Mac, the environment variables aren't synchronised automatically between the
shell and Emacs.
[[https://github.com/purcell/exec-path-from-shell][exec-path-from-shell]] fixes that.

#+begin_src emacs-lisp
(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :config
  (exec-path-from-shell-initialize))
#+end_src

On Mac, I ran into some trouble with my shell, so I specify the shell as a safeguard against random errors.

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  (setq vterm-shell "/opt/homebrew/bin/fish"))
#+end_src

*** DWIM Shell Commands

 [[https://github.com/xenodium/dwim-shell-command][DWIM Shell Commands]] ("Do What I Mean" shell commands) are a collection of command-line utilities integrated with Emacs. We'll load the optional package with pre-configured commands as well.

 #+begin_src emacs-lisp
 (use-package dwim-shell-command
   :defer t
   :init (require 'dwim-shell-commands))
 #+end_src

* Custom Keybindings
** Custom Keymap

I keep a custom keybinding map that I add to per package, and then activate at
the end of the configuration. This keeps my custom bindings from being
overwritten by extensions' own bindings.

The first step is to create the custom keybinding map. We'll add bindings to it
throughout the config, and then activate it at the end of the config file, at
[[#activating-custom-keybindings][Activating Custom Keybindings]].

#+begin_src emacs-lisp
(defvar custom-bindings-map (make-keymap)
  "A keymap for custom keybindings.")
#+end_src

** Mac OS Modifier Keys

On a Mac, I would want to add some specific settings. As a note to myself, I
have the following settings in Mac OS:

#+begin_src sh :tangle no
caps-lock -> control (ctrl)
control   -> control (ctrl)
option    -> option  (alt)
command   -> command (meta)
#+end_src

#+begin_src emacs-lisp
(setq mac-command-modifier       'meta
      mac-right-command-modifier 'meta
      mac-option-modifier        nil
      mac-right-option-modifier  nil)
#+end_src

** Unbind Some Default Keys

Some of the default keybindings are annoying, so let's unbind them.

I never mean to press =C-x C-z=, which hides the current Emacs frame.

I also don't like using =C-<wheel up/down>= to zoom, which I often do accidentally.

And I never want to suspend the Emacs frame with =C-z=.

#+begin_src emacs-lisp
(global-unset-key (kbd "C-x C-z"))
(global-unset-key (kbd "C-<wheel-up>"))
(global-unset-key (kbd "C-<wheel-down>"))
(global-unset-key (kbd "C-z"))
#+end_src

* Visuals
** Decluttering

Let's declutter a little. This should have gone into =early-init.el=, but I get
strange compilation warnings (optimiser says there's too much on the stack).

#+begin_src emacs-lisp
(dolist (mode
         '(tool-bar-mode       ;; Remove toolbar
           scroll-bar-mode     ;; Remove scollbars
           menu-bar-mode))     ;; Remove menu bar
  (funcall mode 0))
#+end_src

This wouldn't go into =early-init= anyways.

#+begin_src emacs-lisp
(setq inhibit-startup-message           t       ;; No startup message
      inhibit-startup-echo-area-message t       ;; No startup message in echo area
      inhibit-startup-screen            t       ;; No default startup screen
      initial-buffer-choice             t       ;; *scratch* is default startup buffer
      initial-major-mode                'fundamental-mode
      ring-bell-function                'ignore ;; No bell
      display-time-default-load-average nil     ;; Don't show me load time
      scroll-margin                     0       ;; Space between top/bottom
      use-dialog-box                    nil)    ;; Disable dialog
#+end_src

** Frames & Windows
*** Open in Fullscreen

When I open Emacs, I want it to open maximised and fullscreen by default.

#+BEGIN_SRC emacs-lisp
(add-to-list 'default-frame-alist     '(fullscreen . maximized))
;; (add-hook 'window-setup-hook          'toggle-frame-fullscreen t)  ;; F11
#+END_SRC

*** Frame Transparency

This doesn't work ideally, but it does the job. I use it very rarely.

#+begin_src emacs-lisp
 (defun toggle-transparency ()
   (interactive)
   (let ((alpha (frame-parameter nil 'alpha)))
     (set-frame-parameter
      nil 'alpha
      (if (eql (cond ((numberp alpha) alpha)
                     ((numberp (cdr alpha)) (cdr alpha))
                     ;; Also handle undocumented (<active> <inactive>) form.
                     ((numberp (cadr alpha)) (cadr alpha)))
               100)
          '(90 . 55) '(100 . 100)))))
 (global-set-key (kbd "C-c h t") 'toggle-transparency)
#+end_src

*** Frame Border

I want a small border around the whole frame, because I think it looks nicer.

#+BEGIN_SRC emacs-lisp
(add-to-list 'default-frame-alist '(internal-border-width . 16))
#+END_SRC

Some settings to fringes.

#+begin_src emacs-lisp
(set-fringe-mode 10)                          ;; Set fringe width to 10

(setq-default fringes-outside-margins nil)
(setq-default indicate-buffer-boundaries nil) ;; Otherwise shows a corner icon on the edge
(setq-default indicate-empty-lines nil)       ;; Otherwise there are weird fringes on blank lines

(set-face-attribute 'header-line t :inherit 'default)
#+end_src

*** Title Bar on Mac OS

I use [[https://github.com/d12frosted/homebrew-emacs-plus][Emacs Plus]] port for Mac OS. With it, you can get a transparent title bar (i.e., title bar is same colour as theme background) which I think is really nice.

First, install Emacs Plus.

#+begin_src shell :tangle no
# enable tap
brew tap d12frosted/emacs-plus

# install
brew install emacs-plus [options]
#+end_src

Then add the corresponding settings to your =init=-file.

There are two different styles you can choose from. You can have absolutely no title bar on your window or you can have a transparent bar, which still has the three stoplight buttons in the upper-left corner.

For natural title bar, use =ns-transparent-titlebar= and for no title bar, use =undecorated= or =undercorated-round=.

I also set some other options. For example, I don't need info in the title bar about which buffer is in focus, since this info is already in the mode line. I found these options in [[https://notes.alexkehayias.com/emacs-natural-title-bar-with-no-text-in-macos/][this blog post]].

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  ; no title bar
  (add-to-list 'default-frame-alist '(undecorated-round . t))
  ; don't use proxy icon
  (setq ns-use-proxy-icon nil)
  ; don't show buffer name in title bar
  (setq frame-title-format ""))
#+end_src

Finally, in your terminal, run these commands to use transparent title bar and
to hide the icon from the middle of the title bar. I found these in the
aforementioned blog post and in the Emacs-Mac Port's
[[https://github.com/railwaycat/homebrew-emacsmacport/wiki/Natural-Title-Bar][wiki page]] on the subject.

#+begin_src shell :tangle no
# for dark themes
defaults write org.gnu.Emacs TransparentTitleBar DARK

# for light themes
defaults write org.gnu.Emacs TransparentTitleBar LIGHT

# hide document icon from title bar
defaults write org.gnu.Emacs HideDocumentIcon YES
#+end_src

*** Plain Title Bar on GNOME

On GNOME, I can't get a transparent/native title bar. But I can remove the text from the middle, so it's completely plain.

#+begin_src emacs-lisp
(when (eq system-type 'gnu/linux)
  ; don't show buffer name in title bar
  (setq frame-title-format nil)
  ; no title bar
  (add-to-list 'default-frame-alist '(undecorated . t))
  ; add small border to enable drag/resize
  (add-to-list 'default-frame-alist '(drag-internal-border . 1)))
#+end_src

** Programming-Specific Visuals
*** Cursor

I prefer a non-blinking bar cursor (over a block cursor).

#+begin_src emacs-lisp
(blink-cursor-mode        0)
(setq-default cursor-type 'bar)
#+end_src

Also, don't render cursors or regions in non-focused windows.

 #+begin_src emacs-lisp
 (setq-default cursor-in-non-selected-windows nil)
 (setq highlight-nonselected-windows          nil)
 #+end_src

Having a thin cursor can make it hard to see where you are after switching buffers or jumping around. [[https://github.com/Malabarba/beacon][Beacon]] highlights your cursor temporarily, which immediately answers the question "Woah, where am I now?"

#+begin_src emacs-lisp
(use-package beacon
  :defer t
  :init  (beacon-mode 1)
  :bind (:map custom-bindings-map ("C-:" . beacon-blink))
  :config
  (setq beacon-blink-when-window-scrolls nil))
#+end_src

*** Styling Delimiters

When coding, I want my delimiters (parentheses, brackets, etc.) to be colourised
in pairs. [[https://github.com/Fanael/rainbow-delimiters/tree/7919681b0d883502155d5b26e791fec15da6aeca][rainbow-delimiters]] does exactly that.

#+begin_src emacs-lisp
(use-package rainbow-delimiters
  :hook (prog-mode-hook . rainbow-delimiters-mode))
#+end_src

Also, please highlight matching parentheses/delimiters.

#+begin_src emacs-lisp
(show-paren-mode t) ;; Highlight matching parentheses
#+end_src

*** Line Numbers

I usually only need line numbers in programming mode.

#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
#+END_SRC

Also, set the default line number width to 3. This prevents the fringe "jumping" when switching from 2-digit line numbers to 3. I could set it to 4, but I don't want that much space taken up by default, since not all files will be 1000+ lines, while many files are 100+ lines.

#+begin_src emacs-lisp
(setq-default display-line-numbers-width 3)
#+end_src

*** Uniquify Buffers

When opening the files =foo/bar/name= and =baz/bar/name=, use forward slashes to
distinguish them. Default behaviour is angle brackets, which would yield =name<foo/bar>= and =name<baz/bar>=..

#+begin_src emacs-lisp
(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)
#+end_src

*** Highlight Long Lines

Highlight lines over 120 characters long.

#+begin_src emacs-lisp
(setq my-whitespace-style '(face tabs lines-tail)
      whitespace-style my-whitespace-style
      whitespace-line-column 120
      fill-column 120
      whitespace-display-mappings
      '((space-mark 32 [183] [46])
        (newline-mark 10 [36 10])
        (tab-mark 9 [9655 9] [92 9])))

;; in e.g. clojure-mode-hook
;; (whitespace-mode 1)
;; or globally
;; (global-whitespace-mode 1)
(add-hook 'prog-mode 'whitespace-mode)
#+end_src

** Fonts
*** Default, Fixed, and Variable Fonts

Please note that I scale and set Org-specific faces in the [[#visuals-1][Org > Visuals]] section.

For the fixed-pitch font, I'm using the excellent [[https://github.com/weiweihuanghuang/fragment-mono][Fragment Mono]], which
has great ligature support.

I have Open Sans configured as my variable-pitch font.

#+begin_src emacs-lisp
(defvar soph/font-height 115)

(when (eq system-type 'darwin)
  (setq soph/font-height 120))

(when (member "Fragment Mono" (font-family-list))
  (set-face-attribute 'default nil :font "Fragment Mono" :height soph/font-height)
  (set-face-attribute 'fixed-pitch nil :family "Fragment Mono"))

(when (member "Open Sans" (font-family-list))
  (set-face-attribute 'variable-pitch nil :family "Open Sans"))
#+end_src

*** Mixed Pitch Fonts

[[https://gitlab.com/jabranham/mixed-pitch][mixed-pitch]] allows you to mix fixed and variable pitched faces in Org and LaTeX mode.

#+begin_src emacs-lisp
(use-package mixed-pitch
  :defer t
  :hook ((org-mode   . mixed-pitch-mode)
         (LaTeX-mode . mixed-pitch-mode)))
#+end_src

*** Ligatures

The package =ligature.el= provides support for displaying the ligatures of
fonts that already have ligatures. Mine does, and seems to work just fine out
of the box with the ligatures defined on the package's page,

#+begin_src emacs-lisp
(defvar ligature-def '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                       ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                       "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                       "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                       "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                       "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                       "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                       "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                       ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                       "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                       "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                       "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                       "\\\\" "://"))

(use-package ligature
  :config
  (ligature-set-ligatures 'prog-mode ligature-def)
  (global-ligature-mode t))
#+end_src

*** Zoom

By default, when increasing/decreasing the font size, you only change the font size in the current buffer, so the modeline, other windows, and minbuffer keeps their old text sizes.

I often want to scale *all* the text in the UI when I change text size, so let's create some keybindings.

#+begin_src emacs-lisp
(defun soph/increase-font-size ()
  (interactive)
  (set-face-attribute 'default nil :height (+ (face-attribute 'default :height) 10)))

(defun soph/decrease-font-size ()
  (interactive)
  (set-face-attribute 'default nil :height (- (face-attribute 'default :height) 10)))

(defun soph/reset-font-size ()
  (interactive)
  (set-face-attribute 'default nil :height soph/font-height))

(define-key custom-bindings-map (kbd "C-x C-+") 'soph/increase-font-size)
(define-key custom-bindings-map (kbd "C-x C--") 'soph/decrease-font-size)
(define-key custom-bindings-map (kbd "C-x C-0") 'soph/reset-font-size)
#+end_src

** Icons & Emojis

Add =nerd-icons=.

#+begin_src emacs-lisp
(use-package nerd-icons)
#+end_src

I also want to be able to display emojis with the Apple emoji font. I usually
don't use it, though, so I won't activate the global mode.

#+BEGIN_SRC emacs-lisp
(use-package emojify
  :config
  (when (member "Apple Color Emoji" (font-family-list))
    (set-fontset-font
      t 'symbol (font-spec :family "Apple Color Emoji") nil 'prepend)))
#+END_SRC

** Themes

I really like the =doom-themes= package, in particular their port of the [[https://www.nordtheme.com/][Nord theme]].

#+BEGIN_SRC emacs-lisp
(use-package doom-themes
  :config
  (setq doom-themes-enable-bold t     ; if nil, bold is universally disabled
        doom-themes-enable-italic t)) ; if nil, italics is universally disabled
#+END_SRC

I also have a custom light theme I'm working on called South. Let's pull it down from GitHub.

#+begin_src emacs-lisp
(use-package south-theme
  :vc (:url "https://github.com/SophieBosio/south"
       :rev :newest
       :branch "main"))
#+end_src

And I don't want Emacs to ask me before changing to one of the themes I've used before.

#+begin_src emacs-lisp
(setq custom-safe-themes t)
#+end_src

*** Default Dark & Light Themes

My favourite dark theme is =doom-nord=. I haven't been able to find any light themes I really love, so I made South to act as Nord's bright counterpart. I'll set these two as my default dark and light themes respectively, and load the dark theme by default.

I'll also define a default accent colour, which is used in packages like =eval-sexp-fu=, or wherever I need to define a popping colour outside the theme itself.

#+begin_src emacs-lisp
(defvar soph/default-dark-theme  'doom-nord)
(defvar soph/default-light-theme 'south)

(defvar soph/default-dark-accent-colour  "SkyBlue4")
(defvar soph/default-light-accent-colour "#D9EDFC")

(load-theme soph/default-dark-theme t)
#+end_src

*** Changing Theme With System Theme

[[https://github.com/LionyxML/auto-dark-emacs][auto-dark-emacs]] is a package for switching themes with the system theme. It works both on Linux and on MacOS.

In the hook, I'll set the colour of the =eval-sexp-fu= flash to the =default-{dark/light}-accent-colour=.

For some reason, my light themes leave some fragments that disappear when I load the theme twice, so I'll do that too.

#+begin_src emacs-lisp
(use-package autothemer
  :defer t)

(use-package auto-dark
  :ensure t
  :hook ((auto-dark-dark-mode
          .
          (lambda ()
            (interactive)
            (progn
              (custom-set-faces
               `(eval-sexp-fu-flash
                 ((t (:background
                      ,soph/default-dark-accent-colour)))))
              (load-theme soph/default-dark-theme t))))
         (auto-dark-light-mode
          .
          (lambda ()
            (interactive)
            (progn
              (custom-set-faces
               `(eval-sexp-fu-flash
                 ((t (:background
                      ,soph/default-light-accent-colour)))))
              (load-theme soph/default-light-theme t)))))
  :custom
  (auto-dark-themes                   `((,soph/default-dark-theme) (,soph/default-light-theme)))
  (auto-dark-polling-interval-seconds 5)
  (auto-dark-allow-osascript          t)
  :init (auto-dark-mode t))
#+end_src

We can even change the system theme from within Emacs using a [[https://github.com/xenodium/dwim-shell-command/][dwim-shell-command]] for Mac OS. The Gnome extension [[https://nightthemeswitcher.romainvigier.fr/][Night Theme Switcher]] takes care of things on my Linux machine.

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  (define-key custom-bindings-map (kbd "M-T") 'dwim-shell-commands-macos-toggle-dark-mode))
#+end_src

*** Conflict-Free Theme Changing

When changing themes interactively, as with =M-x load-theme=, the current custom theme is not disabled and this causes some weird issues. For example, the borders around posframes disappear. [[https://github.com/larstvei/dot-emacs?tab=readme-ov-file#advice][This snippet]] from Lars' config advises =load-theme= to always disable the currently enabled themes before switching.

#+begin_src emacs-lisp
(defun disable-custom-themes (theme &optional no-confirm no-enable)
  (mapc 'disable-theme custom-enabled-themes))

(advice-add 'load-theme :before #'disable-custom-themes)
#+end_src

** Mode Line
*** Column Number

Show current column number in mode line.

#+begin_src emacs-lisp
(column-number-mode t) ;; Show current column number in mode line
#+end_src

*** Custom Mode Line

Customising the default mode line is thankfully pretty easy. Note that I use the
[[https://github.com/rainstormstudio/nerd-icons.el][nerd-icons]] package for the VC branch symbol in the code below. I've also
borrowed some code from [[https://amitp.blogspot.com/2011/08/emacs-custom-mode-line.html][this blog post]] by Amit Patel on writing a custom mode
line.

This mode line is heavily inspired by Nicolas Rougier's [[https://github.com/rougier/nano-modeline][Nano Modeline]] and he
even helped me figure out [[https://www.reddit.com/r/emacs/comments/18ktlkg/comment/kdx3aam/?context=3][how to add vertical padding]] to it.

It has this shape:
=[ lambda <filename> <git branch name>               <Flycheck errors> LLLL:CCCC ]=

First, a variable to hold the Flycheck mode line string and a function to calculate it.

#+begin_src emacs-lisp
(defvar soph/flycheck-mode-line-string nil
  "String showing current Flycheck error counts.")

(defun soph/update-flycheck-modeline ()
  "Update the Flycheck modeline string with current error counts."
  (setq soph/flycheck-mode-line-string
        (if (and (bound-and-true-p flycheck-mode)
                 flycheck-current-errors)
            (let* ((error-counts (flycheck-count-errors flycheck-current-errors))
                   (errors 0)
                   (warnings 0)
                   (infos 0)
                   (parts '()))
              ;; Count error types, both native Flycheck errors and ones from lsp-mode
              (dolist (item error-counts)
                (let ((level (symbol-name (car item)))
                      (count (cdr item)))
                  (cond
                   ((or (eq (car item) 'error)
                        (string-prefix-p "lsp-flycheck-error" level))
                    (setq errors (+ errors count)))
                   ((or (eq (car item) 'warning)
                        (string-prefix-p "lsp-flycheck-warning" level))
                    (setq warnings (+ warnings count)))
                   ((or (eq (car item) 'info)
                        (string-prefix-p "lsp-flycheck-info" level))
                    (setq infos (+ infos count))))))
              (when (> infos 0)
                (push (propertize (format "%d" infos) 'face 'success)
                      parts))
              (when (> warnings 0)
                (push (propertize (format "%d" warnings) 'face 'warning)
                      parts))
              (when (> errors 0)
                (push (propertize (format "‍%d" errors) 'face 'error)
                      parts))
              (if parts
                  (concat (propertize "✱ " 'face 'font-lock-comment-face)
                          (string-join (nreverse parts) (propertize " / " 'face 'font-lock-comment-face))
                          (propertize " ✱ " 'face 'font-lock-comment-face))
                (propertize " - " 'face 'font-lock-comment-face)))
          (propertize " - " 'face 'font-lock-comment-face)))
  (force-mode-line-update))
#+end_src

And then the mode line definition itself:

#+begin_src emacs-lisp
(setq-default mode-line-format
  '("%e"
	(:propertize " " display (raise +0.4)) ;; Top padding
	(:propertize " " display (raise -0.4)) ;; Bottom padding

	(:propertize "λ " face font-lock-comment-face)
	mode-line-frame-identification
	mode-line-buffer-identification

	;; Version control info
	(:eval (when-let (vc vc-mode)
			 ;; Use a pretty branch symbol in front of the branch name
			 (list (propertize "   " 'face 'font-lock-comment-face)
                   ;; Truncate branch name to 50 characters
				   (propertize (truncate-string-to-width
                                (substring vc 5) 50)
							   'face 'font-lock-comment-face))))

	;; Add space to align to the right
	(:eval (propertize
			 " " 'display
			 `((space :align-to
					  (-  (+ right right-fringe right-margin)
						 ,(+ 3
                             (string-width (or soph/flycheck-mode-line-string ""))
                             (string-width "%4l:3%c")))))))

    ;; Flycheck error counts
    (:eval (or soph/flycheck-mode-line-string ""))
	
	;; Line and column numbers
	(:propertize "%4l:%c" face mode-line-buffer-id)))
#+end_src

*** Hide Mode Line

[[https://github.com/hlissner/emacs-hide-mode-line][hide-mode-line-mode]] is extracted from Doom Emacs, and does what it says on the tin. It can also be added to hooks to hide the mode line in certain modes. I have it bound to =C-c h m= - mneumonically "User command: Hide Modeline".

#+begin_src emacs-lisp
(use-package hide-mode-line
  :defer t
  :bind (:map custom-bindings-map ("C-c h m" . hide-mode-line-mode)))
#+end_src

** Text Display Modes
*** Olivetti

[[https://github.com/rnkn/olivetti][Olivetti]] is a minor mode for centering text. For convenience, I'll bind it to =C-c o= to activate/deactivate it on the fly.

#+BEGIN_SRC emacs-lisp
(use-package olivetti
  :defer t
  :config
  (setq olivetti-style      t
        olivetti-body-width 100))
#+END_SRC

*** Adaptive Wrap

In addition, I use [[https://elpa.gnu.org/packages/adaptive-wrap.html][adaptive-wrap]] to visually wrap lines.

#+begin_src emacs-lisp
(use-package adaptive-wrap
  :defer t
  :hook (visual-line-mode . adaptive-wrap-prefix-mode))
#+end_src

*** Writeroom Mode

[[https://github.com/joostkremers/writeroom-mode][Writeroom Mode]] gives you a distraction-free writing environment.

#+begin_src emacs-lisp
(use-package writeroom-mode
  :defer t)
#+end_src

*** Focus

 [[https://github.com/larstvei/Focus][Focus]] dims surrounding text in a semantic manner (sentences,
 paragraphs, sections, code blocks, etc.) making it easier to, well,
 focus. I find this especially helpful when editing LaTeX.

 #+begin_src emacs-lisp
 (use-package focus
   :defer t)
 #+end_src

* General Editing
** Built-In Options

#+BEGIN_SRC emacs-lisp
(delete-selection-mode   t) ;; Replace selected text when yanking
(global-so-long-mode     t) ;; Mitigate performance for long lines
(global-visual-line-mode t) ;; Break lines instead of truncating them
(global-auto-revert-mode t) ;; Revert buffers automatically when they change
(recentf-mode            t) ;; Remember recently opened files
(savehist-mode           t) ;; Remember minibuffer prompt history
(save-place-mode         t) ;; Remember last cursor location in file
(repeat-mode             t) ;; Repeat last command without prefix
#+END_SRC

Recenter the window after restoring with =savehist-mode=. Found in [[https://emacsredux.com/blog/2026/04/07/stealing-from-the-best-emacs-configs/][Bozhidar Batsov's blog post 'Stealing from the Best Emacs Config']].

#+begin_src emacs-lisp
(advice-add 'save-place-find-file-hook :after
            (lambda (&rest _)
              (when buffer-file-name (ignore-errors (recenter)))))
#+end_src

#+BEGIN_SRC emacs-lisp
(setq auto-revert-interval                1    ;; Refresh buffers fast
      auto-revert-verbose                 nil  ;; Don't notify me about reverts
      echo-keystrokes                     0.1  ;; Show keystrokes fast
      frame-inhibit-implied-resize        1    ;; Don't resize frame implicitly
      sentence-end-double-space           nil  ;; No double spaces
      recentf-max-saved-items             1000 ;; Show more recent files
      use-short-answers                   t    ;; 'y'/'n' instead of 'yes'/'no' etc.
      history-length                      25   ;; Only save the last 25 minibuffer prompts
      set-mark-command-repeat-pop         t    ;; Pop successive marks with C-<space>
      global-auto-revert-non-file-buffers t)   ;; Revert Dired and other buffers
#+END_SRC

#+BEGIN_SRC emacs-lisp
(setq-default tab-width 4)  ;; Smaller tabs
#+END_SRC

#+begin_src emacs-lisp
;; Don't try to ping hostnames when looking for files with `find-file-at-point`
(setq ffap-machine-p-known 'reject)
#+end_src

** Scrolling

I want scrolling to be a *lot* slower than it is by default.

#+begin_src emacs-lisp
(setq scroll-conservatively            101
      mouse-wheel-follow-mouse         't
      mouse-wheel-progressive-speed    nil
      ;; Scroll 1 line at a time, instead of default 5
      ;; Hold shift to scroll faster and meta to scroll very fast
      mouse-wheel-scroll-amount        '(1 ((shift) . 3) ((meta) . 6)))
#+end_src

And I want it to be smooth!

#+begin_src emacs-lisp
(pixel-scroll-mode)
(pixel-scroll-precision-mode)

(setq mac-redisplay-dont-reset-vscroll t
      mac-mouse-wheel-smooth-scroll    nil)
#+end_src

** Tabs & Indentation

One of the things that drove me the most insane when I first downloaded Emacs,
was the way it deals with indentation.

I want to use spaces instead of tabs. But if I'm working on a project that does
use tabs, I don't want to mess with other people's code, so I've used this
[[https://www.emacswiki.org/emacs/NoTabs][snippet]] from the Emacs Wiki to infer indentation style.

#+begin_src emacs-lisp
(defun infer-indentation-style ()
  "Default to no tabs, but use tabs if already in project"
  (let ((space-count (how-many "^  " (point-min) (point-max)))
        (tab-count   (how-many "^\t" (point-min) (point-max))))
    (if (> space-count tab-count) (setq-default indent-tabs-mode nil))
    (if (> tab-count space-count) (setq-default indent-tabs-mode t))))

(setq-default indent-tabs-mode nil)
(infer-indentation-style)
#+end_src

Set backtab to =indent-rigidly-left=. Then I can easily unindent regions that use
spaces instead of tabs.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "<backtab>") 'indent-rigidly-left)
#+end_src

And finally, make backspace remove the whole tab instead of just deleting one space.

#+begin_src emacs-lisp
(setq backward-delete-char-untabify-method 'hungry)
#+end_src

** Kill Ring

Another thing that bothered me, was how the =backward-kill-word= command
(C-delete/backspace) would delete not only trailing backspaces, but everything
behind it until it had deleted a word. Additionally, this was automatically
added to the kill ring. With this the help of some regexps, it behaves more like normal Ctrl-Backspace.

The code is taken from [[https://emacs.stackexchange.com/a/30404][this]] and [[https://stackoverflow.com/a/12990359][this]] Stack Exchange/Overflow post.

#+BEGIN_SRC emacs-lisp
(defun soph/delete-dont-kill (arg)
  "Delete characters backward until encountering the beginning of a word.
   With argument ARG, do this that many times. Don't add to kill ring."
  (interactive "p")
  (delete-region (point) (progn (backward-word arg) (point))))

(defun soph/backward-delete ()
  "Delete a word, a character, or whitespace."
  (interactive)
  (cond
   ;; If you see a word, delete all of it
   ((looking-back (rx (char word)) 1)
    (soph/delete-dont-kill 1))
   ;; If you see a single whitespace and a word, delete both together
   ((looking-back (rx (seq (char word) (= 1 blank))) 1)
	(soph/delete-dont-kill 1))
   ;; If you see several whitespaces, delete them until the next word
   ((looking-back (rx (char blank)) 1)
    (delete-horizontal-space t))
   ;; If you see a single non-word character, delete that
   (t
    (backward-delete-char-untabify 1))))
#+END_SRC

Let's bind this in my custom keybindings map.

#+begin_src emacs-lisp
(define-key custom-bindings-map [C-backspace] 'soph/backward-delete)
#+end_src

Speaking of killing text, it's nice to be able to browse the kill ring.

#+begin_src emacs-lisp
(use-package browse-kill-ring
  :defer t)
#+end_src

And I don't need any duplicates in that kill ring.

#+begin_src emacs-lisp
(setq kill-do-not-save-duplicates t)
#+end_src

I also want to save the contents of my clipboard (copied in other programs) before I kill something in Emacs, so it doesn't get overwritten.

#+begin_src emacs-lisp
(setq save-interprogram-paste-before-kill t)
#+end_src

And finally, please persist the kill ring between sessions.

#+begin_src emacs-lisp
(setq savehist-additional-variables
      '(search-ring regexp-search-ring kill-ring))

;; Drop fontifcation of kill ring entries
(add-hook 'savehist-save-hook
          (lambda ()
            (setq kill-ring
                  (mapcar #'substring-no-properties
                          (cl-remove-if-not #'stringp kill-ring)))))
#+end_src

** Auto-Saving

To avoid clutter, let's put all the auto-saved files into one and the same directory.

#+BEGIN_SRC emacs-lisp
(defvar emacs-autosave-directory
  (concat user-emacs-directory "autosaves/")
  "This variable dictates where to put auto saves. It is set to a
  directory called autosaves located wherever your .emacs.d/ is
  located.")

;; Sets all files to be backed up and auto saved in a single directory.
(setq backup-directory-alist
      `((".*" . ,emacs-autosave-directory))
      auto-save-file-name-transforms
      `((".*" ,emacs-autosave-directory t)))
#+END_SRC

I prefer having my files save automatically. Any changes I don't want, I just
don't commit to git. I use =auto-save-buffers-enhanced= to automatically save all
buffers, not just the ones I have open.

But since saving this file - the =init.org=-file - triggers recompilation of
=init.el=, it's really annoying if this file is autosaved when I write to it.
Therefore, I'll disable automatic saving for this file in particular.

Normally, I have auto-saving on for programming, but it's super annoying when writing JS/TS because it keeps reformatting my code as I type. So let's turn it off and save manually instead.

#+BEGIN_SRC emacs-lisp
(use-package auto-save-buffers-enhanced
  :ensure t
  :config
  (auto-save-buffers-enhanced t)
  (setq auto-save-buffers-enhanced-exclude-regexps
        '("init.org"
          "\\(\\.js\\|\\.ts\\|\\.jsx\\|\\.tsx\\|\\.html\\)$")))
#+END_SRC

** Move Where I Mean

[[https://github.com/alezost/mwim.el][mwim]] (Move Where I Mean) takes semantics and indentation into account. This lets us rebind =C-a= and =C-e= to move to the beginning and end of a line while respecting indentation. I.e., don't move to the actual beginning of the line, but to indentation.

#+begin_src emacs-lisp
(use-package mwim
  :ensure t
  :bind (:map custom-bindings-map
              ("C-a" . mwim-beginning-of-code-or-line)
              ("C-e" . mwim-end-of-code-or-line)))
#+end_src

** Text Editing Functions
*** Expand Region

[[https://github.com/magnars/expand-region.el][expand-region]] expand the region (selected text) with semantic units (e.g.,
symbol, word, sentence, paragraph). It's super handy!

=M-q= is bound to =fill-paragraph=. I don't use that binding, but you might want to bind this to a different key combo if you do.

#+begin_src emacs-lisp
(use-package expand-region
  :defer t
  :bind (:map custom-bindings-map
              ("M-q" . er/expand-region)
              ("M-'" . er/contract-region)))
#+end_src

*** Filling/Unfilling

In Emacs, paragraphs can be padded by a bunch of newlines, meaning a what looks like a normal paragraph in Emacs (one line) is actually several lines with =\n= all over. This function removes those and makes the selected region one line again.

#+begin_src emacs-lisp
;;; Stefan Monnier <foo at acm.org>. It is the opposite of fill-paragraph
(defun unfill-paragraph (&optional region)
  "Takes a multi-line paragraph and makes it into a single line of text."
  (interactive (progn (barf-if-buffer-read-only) '(t)))
  (let ((fill-column (point-max))
		;; This would override `fill-column' if it's an integer.
		(emacs-lisp-docstring-fill-column t))
	(fill-paragraph nil region)))
;; Handy key definition
(define-key custom-bindings-map (kbd "C-c n q") 'unfill-paragraph)
#+end_src

*** Multiple Cursors & Symbol Overlay

[[https://github.com/magnars/multiple-cursors.el][multiple-cursors]] makes life so much easier! I often use it to create several cursors directly above one another. I'll trust myself to wield this power responsibly and set the variable =mc/always-run-for-all= to =t=, which disables the default behaviour prompting the user for confirmation when trying to do certain things with the multiple cursors.

#+begin_src emacs-lisp
(use-package multiple-cursors
  :defer t
  :functions
  mc/remove-fake-cursors
  mc/save-excursion
  mc/create-fake-cursor-at-point
  mc/maybe-multiple-cursors-mode
  :bind (:map custom-bindings-map
              ("M-n" . mc/mark-next-like-this)
              ("M-p" . mc/mark-previous-like-this))
  :config
  (setq mc/always-run-for-all t))
#+end_src

[[https://github.com/wolray/symbol-overlay][symbol-overlay]] highlights all occurrences of the symbol at point and allows to jump between them.

#+begin_src emacs-lisp
(use-package symbol-overlay
  :defer t
  :functions
  symbol-overlay-put
  symbol-overlay-mode
  :hook (prog-mode . symbol-overlay-mode)
  :bind (:map custom-bindings-map
              ("C-;" . symbol-overlay-put)
              ("M-N" . symbol-overlay-jump-next)
              ("M-P" . symbol-overlay-jump-prev)))
#+end_src

[[https://xenodium.com/its-all-up-for-grabs-and-it-compounds/][In his blog post]], Alvaro Ramirez (AKA Xenodium) demonstrates one of the best things in Emacs: Seeing things that are almost the way you want them and tweaking them with Elisp so they become that. He takes multiple-cursors and symbol-overlay and combines them. and Ramirez wrote a function that lets symbol-overlay communicate to multiple-cursors that this is where you should give me cursors. Edit all the things at once!  I think it's great, so let's use it and bind it to =C-;=.

#+begin_src emacs-lisp
(defun ar/mc-mark-all-symbol-overlays ()
  "Mark all symbol overlays using multiple cursors."
  (interactive)
  (mc/remove-fake-cursors)
  (when-let* ((overlays (symbol-overlay-get-list 0))
              (point (point))
              (point-overlay (seq-find
                              (lambda (overlay)
                                (and (<= (overlay-start overlay) point)
                                     (<= point (overlay-end overlay))))
                              overlays))
              (offset (- point (overlay-start point-overlay))))
    (setq deactivate-mark t)
    (mapc (lambda (overlay)
            (unless (eq overlay point-overlay)
              (mc/save-excursion
               (goto-char (+ (overlay-start overlay) offset))
               (mc/create-fake-cursor-at-point))))
          overlays)
    (mc/maybe-multiple-cursors-mode)))

(define-key custom-bindings-map (kbd "C-M-;") 'ar/mc-mark-all-symbol-overlays)
#+end_src

*** Undo/Redo

The default "undo until you can redo" behaviour of Emacs still trips me up.
[[https://github.com/emacsmirror/undo-fu][undo-fu]] lets me specify keys to "only undo" or "only redo".

#+begin_src emacs-lisp
(use-package undo-fu
  :defer t
  :bind (:map custom-bindings-map
              ("C-_" . undo-fu-only-undo)
              ("M-_" . undo-fu-only-redo)))
#+end_src

*** Move Lines

[[https://github.com/wyuenho/move-dup][move-dup]] provides bindings for moving and duplicating whole lines. It's super convenient.

#+begin_src emacs-lisp
(use-package move-dup
  :bind (:map custom-bindings-map
              (("C-S-<up>"    . move-dup-move-lines-up)
               ("C-S-<down>"  . move-dup-move-lines-down)
               ("M-W"         . move-dup-duplicate-down))))
#+end_src

*** Join Lines

The normal =join-line= joins the current line with the line above it.

=C-j= is normally bound to =electric-newline-and-maybe-indent= but I use =electric-indent-mode= so I have that behaviour when creating newlines by default, so I feel okay rebinding it.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-j") 'join-line)
#+end_src

In [[http://whattheemacsd.com/key-bindings.el-03.html][What the .emacs.d!?]], Magnar shows that passing =-1= as the argument to =join-line= changes which line you join with which. This joins the current line with the one /below/ it instead of the one above.

#+begin_src emacs-lisp
(define-key custom-bindings-map
            (kbd "M-j")
            (lambda ()
              (interactive)
              (join-line -1)))
#+end_src

*** Kill Whole Line

#+begin_src emacs-lisp
(unbind-key "C-S-k")
(define-key custom-bindings-map (kbd "C-S-k") 'kill-whole-line)
#+end_src

** CRUX

[[https://github.com/bbatsov/crux][CRUX]] is a Collection of Ridiculously Useful eXtensions for Emacs. It has a whole bunch of commands and I'd recommend looking into all the things it supports.

#+begin_src emacs-lisp
(use-package crux
  :defer t
  :bind (:map custom-bindings-map
         ("C-S-<return>" . crux-smart-open-line-above)
         ("M-S-<down>"   . crux-duplicate-current-line-or-region)))
#+end_src

** Make Scripts Executable

When creating a script, automatically do a =chmod +x= so it becomes executable.

#+begin_src emacs-lisp
(add-hook 'after-save-hook
          #'executable-make-buffer-file-executable-if-script-p)
#+end_src

* Buffers & Navigation
** Killing Buffers

Sometimes, I'm putting some work away and I don't want those files to show up in
the buffer list. Killing a buffer with =C-x k= or marking several buffers in the
buffer list to kill them is fine, but can be a bit cumbersome.

I found this function in a [[https://superuser.com/questions/895920/how-can-i-close-all-buffers-in-emacs][Stack Exchange answer]]. It allows me to close the
current buffer easily by pressing =C-c k=. If I prefix it, by writing =C-u C-c k=, then
all "interesting" buffers are killed, leaving internal Emacs buffers intact.
This cleans up all the buffers I've opened or used myself.

#+begin_src emacs-lisp
(defun soph/kill-buffer (&optional arg)
"When called with a prefix argument -- i.e., C-u -- kill all interesting
buffers -- i.e., all buffers without a leading space in the buffer-name.
When called without a prefix argument, kill just the current buffer
-- i.e., interesting or uninteresting."
(interactive "P")
  (cond
    ((and (consp arg) (equal arg '(4)))
      (mapc
        (lambda (x)
          (let ((name (buffer-name x)))
            (unless (eq ?\s (aref name 0))
              (kill-buffer x))))
        (buffer-list)))
    (t
      (kill-buffer (current-buffer)))))

(define-key custom-bindings-map (kbd "C-c k") 'soph/kill-buffer)
#+end_src

** Splitting Windows

I want maximum two windows by default. I have a function, taken from [[https://emacs.stackexchange.com/a/40517][this Stack
Overflow post]], that rewrites the =split-window-sensibly= function to reverse its
preference and essentially prefer splitting side-by-side.

#+begin_src emacs-lisp
(defun split-window-sensibly-prefer-horizontal (&optional window)
"Based on `split-window-sensibly', but prefers to split WINDOW side-by-side."
  (let ((window (or window (selected-window))))
    (or (and (window-splittable-p window t)
         ;; Split window horizontally
         (with-selected-window window
           (split-window-right)))
    (and (window-splittable-p window)
         ;; Split window vertically
         (with-selected-window window
           (split-window-below)))
    (and
         ;; If WINDOW is the only usable window on its frame (it is
         ;; the only one or, not being the only one, all the other
         ;; ones are dedicated) and is not the minibuffer window, try
         ;; to split it horizontally disregarding the value of
         ;; `split-height-threshold'.
         (let ((frame (window-frame window)))
           (or
            (eq window (frame-root-window frame))
            (catch 'done
              (walk-window-tree (lambda (w)
                                  (unless (or (eq w window)
                                              (window-dedicated-p w))
                                    (throw 'done nil)))
                                frame)
              t)))
     (not (window-minibuffer-p window))
     (let ((split-width-threshold 0))
       (when (window-splittable-p window t)
         (with-selected-window window
               (split-window-right))))))))

(defun split-window-really-sensibly (&optional window)
  (let ((window (or window (selected-window))))
    (if (> (window-total-width window) (* 2 (window-total-height window)))
        (with-selected-window window (split-window-sensibly-prefer-horizontal window))
      (with-selected-window window (split-window-sensibly window)))))

(setq split-window-preferred-function 'split-window-really-sensibly)
#+end_src

If I have already split the frame into
two windows and then call a function that opens a new window (for example a
Magit or a compilation buffer), then I want Emacs to reuse the inactive window
instead of creating a new one. Setting both =split-height-threshold= and
=split-width-threshold= to =nil= seems to ensure this.

#+begin_src emacs-lisp
(setq-default split-height-threshold nil
              split-width-threshold  nil
              fill-column            80) ;; Maximum line width
              ;; window-min-width       80) ;; No smaller windows than this
#+end_src

** Opening, Closing, & Switching Windows

Opening, switching and deleting windows becomes super easy with [[https://github.com/dimitri/switch-window][switch-window]].

#+begin_src emacs-lisp :tangle no
(use-package switch-window
  :bind (:map custom-bindings-map
              ("C-x o" . 'switch-window)
              ("C-x 1" . 'switch-window-then-maximize)
              ("C-x 2" . 'switch-window-then-split-below)
              ("C-x 3" . 'switch-window-then-split-right)
              ("C-x 0" . 'switch-window-then-delete)))
#+end_src

Automatically resize windows to keep them proportional.

#+begin_src emacs-lisp
(setq window-combination-resize t)
#+end_src

And [[https://www.emacswiki.org/emacs/TransposeFrame][Transpose
Frame]] has some nice functions for shifting windows around. I only really use the
one to swap the left- and right-hand sides of the frame, but there are
others you might find useful.

#+begin_src emacs-lisp
(use-package transpose-frame
  :bind (:map custom-bindings-map
              ("C-c f" . 'flop-frame)))
#+end_src

** Projectile
 
[[https://projectile.mx/][Projectile]] provides a convenient project interaction interface. I keep most of
my projects in a specific folder, so I'll set Projectile to check that path specifically.

#+begin_src emacs-lisp
(use-package projectile
  :defer t
  :bind (:map custom-bindings-map
              ("C-c p" . projectile-command-map)
              ("C-p"   . projectile-command-map))
  :config
  (add-to-list 'projectile-project-search-path "~/Dropbox/projects/")
  (add-to-list 'projectile-project-search-path "~/Dropbox/playground/")
  (setq projectile-create-missing-test-files t)
  :init
  (projectile-mode))
#+end_src

** Javelin

[[https://github.com/DamianB-BitFlipper/javelin.el][Javelin]] is inspired by ThePrimeagen's [[https://github.com/ThePrimeagen/harpoon][Harpoon]] for NeoVim.

It gives you the ability to bookmark files and buffers, scoped to projects and even Git branches.

By default, =M-<number>= will assign a buffer to that binding if not already bookmarked, and jump to the bookmark if already bound.

#+begin_src emacs-lisp
(use-package javelin
  :ensure t
  :bind (:map javelin-minor-mode-map
              ("M--" . nil)
              ("M-+" . javelin-toggle-quick-menu))
  :init
  (global-javelin-minor-mode))
#+end_src

** IBuffer

=ibuffer= is built-in to Emacs. It shows a list of open buffers and lets you perform lots of useful actions, like sorting and killing buffers.

I was inspired by [[https://tech.tonyballantyne.com/2020/09/26/ibuffer-changed-my-life/][this blog post]] by Tony Ballantyne to tidy up the default =ibuffer= view.

I'd like to tidy it up a bit by organising the buffers by major mode.

#+begin_src emacs-lisp
(setq ibuffer-saved-filter-groups
      '(("default"
         ("Programming"   (predicate . (derived-mode-p 'prog-mode)))
         ("Clojure/CIDER" (or (name  . "clojure")
                              (name  . "cider")
                              (name  . "nrepl")))
         ("Org"           (mode . org-mode))
         ("Dired"         (mode . dired-mode))
         ("Magit"         (name . "magit")))))

(add-hook 'ibuffer-mode-hook
          (lambda ()
            (ibuffer-switch-to-saved-filter-groups "default")))
#+end_src

 I also exclude certain system buffers.

#+begin_src emacs-lisp
(setq ibuffer-never-show-predicates
      '(;; System buffers
        "^\\*Messages\\*$"
        "^\\*scratch\\*$"
        "^\\*Completions\\*$"
        "^\\*Help\\*$"
        "^\\*Apropos\\*$"
        "^\\*info\\*$"
        "^\\*Async-native-compile-log\\*$"

        ;; LSP Buffers
        "^\\*lsp-log\\*$"
        "^\\*clojure-lsp\\*$"
        "^\\*clojure-lsp::stderr\\*$"
        "^\\*ts-ls\\*$"
        "^\\*ts-ls::stderr\\*$"))
#+end_src

And finally, I'll remove some columns I don't use.

#+begin_src emacs-lisp
(setq ibuffer-formats
      '((mark " " (name 60 -1 :left))))
#+end_src

** Dired

Emacs's default file manager is nice, but contains a bit more info than I usually need. =dired-hide-details-mode= does what it says on the tin, and I can easily activate/deactivate it on the fly with the default keybinding, =(=.

I'll also bind a few convenience keys. =C-= followed by an arrow moves into a directory/open a file or move up a directory. And lowercase =c= creates/touches a new file and prompts for a name.

The last line is a setting for MacOS telling it to use =gls= when using dired.

#+begin_src emacs-lisp
(use-package dired
  :ensure nil
  :hook (dired-mode . dired-hide-details-mode)
  :bind (:map dired-mode-map
              ("C-<right>" . dired-find-alternate-file)
              ("C-<left>"  . dired-up-directory)
              ("C-<down>"  . dired-find-alternate-file)
              ("C-<up>"    . dired-up-directory)
              ("c"         . dired-create-empty-file))
  :config
  (when (and (eq system-type 'darwin) (executable-find "gls"))
    (setq dired-use-ls-dired nil)))
#+end_src

From [[https://stackoverflow.com/a/55235833][this StackOverflow post]].

#+begin_src emacs-lisp
(put 'dired-find-alternate-file 'disabled nil) ; disables warning
(define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file) ; was dired-advertised-find-file
(define-key dired-mode-map (kbd "^") (lambda () (interactive) (find-alternate-file "..")))  ; was dired-up-directory
#+end_src

* Completion

Emacs distinguishes between two different kinds of completion: complete-at-point  (text/code autocomlete) and completing-read (completion of Emacs commands, file names, etc.).

For completing-read, I use [[https://github.com/minad/vertico][Vertico]] and for completion-at-point at use [[https://company-mode.github.io/][Company]]. I also use a few complimentary packages that enhance the experience.

** Vertico

[[https://github.com/minad/vertico][Vertico]] is heart of this completion UI!

I'll use the function from [[http://whattheemacsd.com/setup-ido.el-02.html][this What the .emacs.d!? post]] which lets me type =~= at the Vertico prompt to go directly to the home directory. For use with Vertico, I add a call to =delete-minibuffer-contents= so that old path is cleared before starting the new file path (starting at =~/=).

#+begin_src emacs-lisp
(defun soph/take-me-home ()
  (interactive)
  (if (looking-back "/" nil)
      (progn (call-interactively 'delete-minibuffer-contents) (insert "~/"))
    (call-interactively 'self-insert-command)))

(use-package vertico
  :defer t
  :bind (:map vertico-map ("~" . soph/take-me-home))
  :config
  (vertico-mode)
  (vertico-multiform-mode)
  (setq read-extended-command-predicate       'command-completion-default-include-p
        vertico-count                         32  ; Show more candidates
        read-file-name-completion-ignore-case t   ; Ignore case of file names
        read-buffer-completion-ignore-case    t   ; Ignore case in buffer completion
        completion-ignore-case                t)) ; Ignore case in completion
#+end_src

** Vertico Posframe

Note: The latest updates to the underlying posframe lib break the visuals of the rounded, themed corners on my Mac, so I pin it directly here to avoid it upgrading to the latest version:

#+begin_src emacs-lisp
(use-package posframe
  :vc (:url "https://github.com/tumashu/posframe"
       :rev "v1.5.1"))
#+end_src

[[https://github.com/tumashu/vertico-posframe][vertico-posframe]] makes Vertico appear in a small child frame, instead of as a
traditional minibuffer. I like to have mine in the middle of the frame, with small fringes on either side.

I temporarily disable =vertico-posframe-mode= when searching with =consult=.
When selecting a search match, a preview is provided. That's kind of hard to see
with the posframe in the middle of the screen, so while searching I just use the
normal minibuffer.

#+begin_src emacs-lisp
(use-package vertico-posframe
  :init
  (setq vertico-posframe-parameters   '((left-fringe  . 12)    ;; Fringes
                                        (right-fringe . 12)
                                        (undecorated  . nil))) ;; Rounded frame
  :config
  (vertico-posframe-mode 1)
  (setq vertico-posframe-width        96                       ;; Narrow frame
        vertico-posframe-height       vertico-count            ;; Default height
        ;; Don't create posframe for these commands
        vertico-multiform-commands    '((consult-line    (:not posframe))
                                        (consult-ripgrep (:not posframe)))))
#+end_src

The rounded frame corners (putting =(undecorated . nil)= in the =vertico-posframe-parameters=) look really nice on Mac OS.

[[./images/vertico-posframe-screenshot.png]]

On Linux, however, this trick doesn't work unless your OS can create rounded windows.

** Corfu

[[https://github.com/minad/corfu][Corfu]] is a completion framework that provides text completion (like in-buffer auto-completion) via pop-up childframes.

It integrates with =Orderless=, the package below this, to provide fuzzy completions.

#+begin_src emacs-lisp
(use-package corfu
  :defer t
  :custom
  (corfu-auto          t)
  (corfu-auto-delay    0.1)
  (corfu-auto-prefix   1)
  (corfu-cycle         t)
  (corfu-quit-no-match 'separator)
  :bind (:map corfu-map
              (" " . corfu-insert-separator)) ;; Option + Space on Norwegian Mac
  :init
  (global-corfu-mode))

(setq tab-always-indent 'complete)
#+end_src

** Orderless

[[https://github.com/oantolin/orderless][Orderless]] is a package for a completion /style/, that matches multiple
regexes, in any order. It gives you fuzzy completions, which are super nice for typing things out faster.

#+begin_src emacs-lisp
(use-package orderless
  :ensure t
  :config
  (setq completion-styles '(orderless basic partial-completion)
        completion-category-overrides '((file (styles basic partial-completion)))))
#+end_src

* Search
** Search Utilities

Projectile also comes with a ton of built-in functionality to search in your projects. Other packages I use also depend on search utilities.

I use both [[https://github.com/BurntSushi/ripgrep][ripgrep]] and [[https://github.com/ggreer/the_silver_searcher][ag]] (The Silver Searcher). [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] also comes in handy sometimes. I'll install all the corresponding Emacs packages.

#+begin_src emacs-lisp
(use-package ripgrep
  :defer t)

(use-package rg
  :defer t)

(use-package ag
  :defer t)

(use-package wgrep
  :defer t)
#+end_src

I want to use [[https://github.com/BurntSushi/ripgrep][ripgrep]] as =grep=.

#+BEGIN_SRC emacs-lisp
(setq grep-command "rg -nS --no-heading "
      grep-use-null-device nil)
#+END_SRC

** Consult

[[https://github.com/minad/consult][Consult]] provides a /ton/ of search, navigation, and completion functionality. I
would definitely recommend looking at the documentation to learn more about all
that it can do.

I often press =C-x C-b= when I only mean to press =C-x b=. If I want to open the list of all buffers, I'll call it with =M-x list-buffers=, so let's rebind this one to the same as =C-x b= so save me some grief.

#+begin_src emacs-lisp
(use-package consult
  :bind (:map custom-bindings-map
              ("C-s"     . consult-line)
              ("C-M-s"   . consult-ripgrep)
              ("C-x b"   . consult-buffer)
              ("C-x C-b" . consult-buffer)
              ("M-g g"   . consult-goto-line)
              ("M-g t"   . consult-imenu)
              ("M-g a"   . consult-imenu-multi)))
#+end_src

** Imenu List

[[https://www.emacswiki.org/emacs/ImenuMode][Imenu]] is a built-in Emacs utility that gives you a minibuffer of the symbols in the current buffer and let's you jump to it. [[https://github.com/bmag/imenu-list][imenu-list]] is a nice package that gives you a new buffer with a navigable list of the functions, vars, etc. in your buffer, allowing you to quickly get an overview or jump to definition.

#+begin_src emacs-lisp
(use-package imenu-list
  :defer t
  :bind (:map custom-bindings-map
              ("M-g i" . imenu-list-smart-toggle)))
#+end_src

** Marginalia

[[https://github.com/minad/marginalia][Marginalia]] gives me annotations in the minibuffer.

#+begin_src emacs-lisp
(use-package marginalia
  :init 
  (marginalia-mode 1))
#+end_src

* Misc. Packages
** Version Control (Magit & Friends)

[[https://github.com/magit/magit][Magit]] is a Git client specifically for Emacs, and it's super powerful. It's the centre of all my version control packages.

*** Git Gutter with =diff-hl=

Let's first make sure we're highlighting uncommitted changes with [[https://github.com/dgutov/diff-hl][diff-hl]]. It highlights added, deleted, and modified code segments by adding a coloured bar to the left-hand gutter of the buffer.

#+begin_src emacs-lisp
(use-package diff-hl
  :config
  (global-diff-hl-mode))
#+end_src

*** Magit

Then configure Magit. I'll add hooks to have =diff-hl= update the gutter whenever Magit refreshes.

#+begin_src emacs-lisp
(use-package magit
  :defer t
  :bind (:map magit-mode-map
              ("C-M-f" . magit-section-forward)
              ("C-M-b" . magit-section-backward))
  :bind (:map custom-bindings-map
              ("M-g b" . magit-blame-addition))
  :hook
  ((magit-pre-refresh  . diff-hl-magit-pre-refresh)
   (magit-post-refresh . diff-hl-magit-post-refresh))
  :config
  (setq magit-mode-quit-window 'magit-restore-window-configuration
		magit-auto-revert-mode t)
  ; Remove tags from status buffer headings to speed up refresh slightly
  (remove-hook 'magit-status-sections-hook 'magit-insert-tags-header)
  ; Remove the "Head", "Merge", and "Push" status headers for even more speed!
  (remove-hook 'magit-status-sections-hook 'magit-insert-status-headers))
#+end_src

*** Magit Forge

And [[https://github.com/magit/forge][Magit Forge]] to be able to work with Git forges (e.g., GitHub, and GitLab) directly from Magit.

#+begin_src emacs-lisp
(use-package forge
  :after magit)
#+end_src

*** Git Link

[[https://github.com/sshaw/git-link/][git-link]] creates URL links to the current position in your buffer in the corresponding forge repo. Super handy for sending to others.

#+begin_src emacs-lisp
(use-package git-link
  :defer t
  :init
  (setq git-link-use-commit t
        git-link-open-in-browser t))
#+end_src

*** Git Timemachine

[[https://codeberg.org/pidu/git-timemachine][Git Time Machine]] lets you step through different versions of a Git-controlled file directly in the current buffer, without even needing to hop over to the Magit status buffer.

#+begin_src emacs-lisp
(use-package git-timemachine
  :defer t)
#+end_src

** Trying Packages

Lars Tveito's [[https://github.com/larstvei/Try][Try]] package lets you try out packages and only save them
temporarily, saving you the hassle of cleaning up afterwards if you decide you
don't want to keep using the package. You can even =try= packages from =.el= files
from URLs directly.

#+begin_src emacs-lisp
(use-package try)
#+end_src

** Snippets

[[https://github.com/joaotavora/yasnippet][YASnippet]] is a template system for Emacs that allows you to predefine snippets
you use often and insert them easily. I want snippets for basic Org-files,
Roam-notes, and other sequences often used.

#+begin_src emacs-lisp
(use-package yasnippet
  :diminish yas-minor-mode
  :defer 5
  :config
  (setq yas-snippet-dirs '("~/.emacs.d/snippets/"))
  (yas-global-mode 1)) ;; or M-x yas-reload-all if you've started YASnippet already.

;; Silences the warning when running a snippet with backticks (runs a command in the snippet)
(require 'warnings)
(add-to-list 'warning-suppress-types '(yasnippet backquote-change)) 
#+end_src

** Better Help Buffers

[[https://github.com/Wilfred/helpful][Helpful]] is an improvement on Emacs' built-in *help* buffer. It's more user-friendly and easier to read.

#+BEGIN_SRC emacs-lisp
(use-package helpful
  :bind (:map custom-bindings-map
			  ("C-h f" . helpful-callable) ; helpful-function++
			  ("C-h v" . helpful-variable)
			  ("C-h k" . helpful-key)
			  ("C-h x" . helpful-command)
			  ("C-h h" . helpful-at-point)))
#+END_SRC

[[https://github.com/justbur/emacs-which-key][which-key]] shows you available keybindings in the minibuffer. When you've started
to enter a command, it will show you where you can go from there.

#+begin_src emacs-lisp
(use-package which-key
  :config
  (which-key-mode))
#+end_src

** Jinx Spellchecker

[[https://github.com/minad/jinx][Jinx]] is a =libenchant=-powered spellchecker with a super nice UI. I'm
trying it out instead of Flyspell, which I used before.

#+begin_src emacs-lisp
(use-package jinx
  :hook (emacs-startup . global-jinx-mode)
  :bind (("M-$"   . jinx-correct)
         ("C-M-$" . jinx-languages))
  :config
  (setq jinx-languages "en_GB"))
#+end_src

** LaTeX

I use [[https://www.gnu.org/software/auctex/][AUCTeX]] to work with LaTeX files from within Emacs and it's a massive help.
It has a lot of different features, and I'd recommend checking out the
documentation to see all the stuff you can do with it.

I also really like =reftex-mode=, which gives you a table of contents with
clickable links for your file with the keybinding =C-c ==.

#+begin_src emacs-lisp
(use-package auctex
  :hook
  (LaTeX-mode . turn-on-prettify-symbols-mode)
  (LaTeX-mode . reftex-mode)
  (LaTeX-mode . outline-minor-mode)
  (LaTeX-mode . olivetti-mode))
#+end_src

When the =reftex= window opens, I want it on the left side of the screen and I
want it to take up less than half the screen.

#+begin_src emacs-lisp
(setq reftex-toc-split-windows-horizontally t
	  reftex-toc-split-windows-fraction     0.2)
#+end_src

** PDF Tools

[[https://github.com/vedang/pdf-tools][PDF Tools]] is an improved version of the built-in DocView for viewing PDFs. It has extensive features, but does not play well with =consult=, so I'll rebind =C-s= to =isearch-forward=.

#+begin_src emacs-lisp
(use-package pdf-tools
  :defer t
  :init (pdf-loader-install)
  :hook ((pdf-view-mode . (lambda () (auto-revert-mode -1)))
         (pdf-view-mode . (lambda () (company-mode -1))))
  :bind (:map pdf-view-mode-map
              ("C-s"   . isearch-forward)
              ("C-M-s" . pdf-occur)))
#+end_src

Warn me when a PDF has been opened with the default DocView mode instead of PDF Tools' PDF View mode.

#+begin_src emacs-lisp
(use-package doc-view
  :hook (doc-view-mode . (lambda ()
                           (display-warning
                            emacs
                            "Oops, using DocView instead of PDF Tools!"
                            :warning))))
#+end_src

[[https://github.com/nicolaisingh/saveplace-pdf-view][saveplace-pdf-view]] is a great package that remembers where in your PDFs you last left off, down to the scroll position and zoom amount.

#+begin_src emacs-lisp
(use-package pdf-view-restore
  :after pdf-tools
  :config
  (add-hook 'pdf-view-mode-hook 'pdf-view-restore-mode))
#+end_src

** EPUBs

[[https://depp.brause.cc/nov.el/][nov.el]] is a package for reading EPUBs (an e-book format) directly in Emacs.

#+begin_src emacs-lisp
(use-package nov
  :defer t
  :config
  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)))
#+end_src

** Editor Config

I want to use the [[https://github.com/editorconfig/editorconfig-emacs][EditorConfig]] plugin, which helps maintain consistent coding
styles across editors when collaborating.

#+begin_src emacs-lisp
(use-package editorconfig
  :defer t)
#+end_src

** Browser Preference

Open links with Firefox by default.

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  (setq browse-url-browser-function 'browse-url-default-macosx-browser))

(when (eq system-type 'gnu/linux)
  (setq browse-url-browser-function 'browse-url-generic
		browse-url-generic-program "firefox"))
#+end_src

** Elfeed

[[https://github.com/skeeto/elfeed][Elfeed]] is a feed reader for Emacs!

#+begin_src emacs-lisp
(use-package elfeed
  :bind (:map custom-bindings-map ("C-x w" . elfeed))
  :config
  (setq elfeed-feeds
      '("http://nullprogram.com/feed/"
        "https://planet.emacslife.com/atom.xml"
        "https://deniskyashif.com/index.xml"
        "https://sophiebos.io/index.xml")))
#+end_src

** Config Profiling

[[https://github.com/jschaf/esup][ESUP]] is a package for profiling your config. You can use it to shave precious seconds off your
startup time, which is useful to me because I keep closing it when I'm done with
a task and then immediately needing it again.

#+begin_src emacs-lisp
(use-package esup
  :defer t
  :config
  (setq esup-depth 0))
#+end_src

* Org

[[https://orgmode.org/][Org Mode]] is a smart text system that is used for organising notes, literate programming, time management, and a wide variety of other use cases. I've been interested in switching from my previous note-taking app, Obsidian, to using Org and Roam (described in the next section).

** Visuals
*** Built-In Options & Hooks

Let's use some of the built-in options in org mode to declutter and get a consistent visual look and feel.

We'll also use [[https://orgmode.org/manual/Special-Symbols.html]["pretty entities"]], which allow us to insert special characters LaTeX-style by using a leading backslash (e.g., =\alpha= to write the greek letter alpha) and display ellipses in a condensed way.

#+BEGIN_SRC emacs-lisp
(use-package org
  :defer t
  :hook (org-mode . olivetti-mode)    ; Center text
  :config
  (org-indent-mode -1)                ; Don't indent text according to heading level
  (setq org-startup-folded   'content ; Fold headings by default
        org-hide-leading-stars      t ; Hide leading stars
        org-pretty-entities         t ; Use LaTeX-style pretty characters
        org-ellipsis            "  ·" ; Character shown after folded headings
        org-src-fontify-natively    t ; Fontify source blocks
        org-src-tab-acts-natively   t ; Have TAB act normally in source blocks
        org-src-content-indentation 0 ; 0 spaces of extra indentation
        org-special-ctrl-a/e        t ; Jump where I mean in Org headings++
        ;; Deal with TODOs and tags more nicely
        org-log-done                t
        org-auto-align-tags         t
        org-tags-column           -80
        ;; Try to act nicely when inserting content
        org-fold-catch-invisible-edits     'show-and-error
        org-insert-heading-respect-content t))
#+END_SRC

*** LaTeX Previews

#+begin_src emacs-lisp
(with-eval-after-load 'org
  ;; Create LaTeX previews on startup
  (setq org-startup-with-latex-preview t)
  ;; Increase size of LaTeX previews in Org mode
  (plist-put org-format-latex-options :scale 1.35))
#+end_src

I had been struggling to get LaTeX previews to work on my work Mac. I symlinked my LaTeX =texbin= directory to =/usr/local/bin=, and it still didn't work. Eventually I found [[https://emacs.stackexchange.com/a/80170][this]] Stack Exchange post that correctly diagnosed the issue.

#+begin_summary Explanation
The error said =[...] Please Adjust 'dvipng' part of 'org-preview-latex-process-alist'.=

The issue is caused by wrong expansion of the relative file path =%f= in the variable =org-preview-latex-process-alist=. Changing it to =%F= resolves the issue.
#+end_summary

#+begin_src emacs-lisp
(with-eval-after-load 'org
  (let ((png (cdr (assoc 'dvipng org-preview-latex-process-alist))))
    (plist-put png :latex-compiler '("latex -interaction nonstopmode -output-directory %o %F"))
    (plist-put png :image-converter '("dvipng -D %D -T tight -o %O %F"))
    (plist-put png :transparent-image-converter '("dvipng -D %D -T tight -bg Transparent -o %O %F"))))
#+end_src

*** Fonts

Set the sizes and fonts for the various headings.

#+begin_src emacs-lisp
;; Resize Org headings
(custom-set-faces
'(org-document-title ((t (:height 1.6))))
'(outline-1          ((t (:height 1.25))))
'(outline-2          ((t (:height 1.2))))
'(outline-3          ((t (:height 1.2))))
'(outline-4          ((t (:height 1.2))))
'(outline-5          ((t (:height 1.2))))
'(outline-6          ((t (:height 1.2))))
'(outline-7          ((t (:height 1.2))))
'(outline-8          ((t (:height 1.2))))
'(outline-9          ((t (:height 1.2)))))
#+end_src

*** Hide Emphasis Markers

Many people hide emphasis markers (e.g., =/.../= for italics, =*...*= for bold,
etc.) to have a cleaner visual look, but I got frustrated trying to go back and
edit text in these markers, as sometimes I would delete the markers itself or
write outside the markers. [[https://github.com/awth13/org-appear][org-appear]] is the solution to all my troubles. It
displays the markers when the cursor is within them and hides them otherwise,
making edits easy while looking pretty.

#+begin_src emacs-lisp
(use-package org-appear
  :commands (org-appear-mode)
  :hook     (org-mode . org-appear-mode)
  :config 
  (setq org-hide-emphasis-markers t)  ;; Must be activated for org-appear to work
  (setq org-appear-autoemphasis   t   ;; Show bold, italics, verbatim, etc.
        org-appear-autolinks      t   ;; Show links
        org-appear-autosubmarkers t)) ;; Show sub- and superscripts
#+end_src

*** Inline Images

Show inline images by default

#+begin_src haskell
(setq org-startup-with-inline-images t)
#+end_src

*** Variable Pitch

Make sure =variable-pitch-mode= is always active in Org buffers. I normally
wouldn't need this, since I use the =mixed-pitch= package in the font section, but
for some reason, it seems the header bullet in Org mode are affected by this.

#+begin_src emacs-lisp
(add-hook 'org-mode-hook 'variable-pitch-mode)
#+end_src

*** LaTeX Fragtog

[[https://github.com/io12/org-fragtog][org-fragtog]] works like org-appear, but for LaTeX fragments: It toggles LaTeX
previews on and off automatically, depending on the cursor position. If you move the
cursor to a preview, it's toggled off so you can edit the LaTeX snippet. When
you move the cursor away, the preview is turned on again.

#+begin_src emacs-lisp
(use-package org-fragtog
  :after org
  :hook (org-mode . org-fragtog-mode))
#+end_src

*** Bullets

=org-superstar= styles some of my UI elements, such as bullets and special
checkboxes for TODOs.

#+begin_src emacs-lisp
(use-package org-superstar
  :after org
  :config
  (setq org-superstar-leading-bullet "")
  (setq org-superstar-headline-bullets-list '("")) ;; Hide bullets
  (setq org-superstar-special-todo-items t)        ;; Makes TODO header bullets into boxes
  (setq org-superstar-todo-bullet-alist '(("TODO"     . 9744)
                                          ("PROG"     . 9744)
                                          ("NEXT"     . 9744)
                                          ("WAIT"     . 9744)
                                          ("DROP"     . 9744)
                                          ("QUESTION" . 9744)
                                          ("DONE"     . 9745)))
  :hook (org-mode . org-superstar-mode))
#+end_src

*** SVG Elements

[[https://github.com/rougier/svg-tag-mode][svg-tag-mode]] lets you replace keywords such as TODOs, tags, and progress bars with nice
SVG graphics. I use it for dates, progress bars, and citations.

#+begin_src emacs-lisp
(use-package svg-tag-mode
  :after org
  :config
  (defconst date-re "[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}")
  (defconst time-re "[0-9]\\{2\\}:[0-9]\\{2\\}")
  (defconst day-re "[A-Za-z]\\{3\\}")
  (defconst day-time-re (format "\\(%s\\)? ?\\(%s\\)?" day-re time-re))

  (defun svg-progress-percent (value)
	(svg-image (svg-lib-concat
				(svg-lib-progress-bar (/ (string-to-number value) 100.0)
			      nil :margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
				(svg-lib-tag (concat value "%")
				  nil :stroke 0 :margin 0)) :ascent 'center))

  (defun svg-progress-count (value)
	(let* ((seq (mapcar #'string-to-number (split-string value "/")))
           (count (float (car seq)))
           (total (float (cadr seq))))
	  (svg-image (svg-lib-concat
				  (svg-lib-progress-bar (/ count total) nil
					:margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
				  (svg-lib-tag value nil
					:stroke 0 :margin 0)) :ascent 'center)))
  (setq svg-tag-tags
      `(;; Org tags
        ;; (":\\([A-Za-z0-9]+\\)" . ((lambda (tag) (svg-tag-make tag))))
        ;; (":\\([A-Za-z0-9]+[ \-]\\)" . ((lambda (tag) tag)))
        
        ;; Task priority
        ("\\[#[A-Z]\\]" . ( (lambda (tag)
                              (svg-tag-make tag :face 'org-priority 
                                            :beg 2 :end -1 :margin 0))))

        ;; Progress
        ("\\(\\[[0-9]\\{1,3\\}%\\]\\)" . ((lambda (tag)
          (svg-progress-percent (substring tag 1 -2)))))
        ("\\(\\[[0-9]+/[0-9]+\\]\\)" . ((lambda (tag)
          (svg-progress-count (substring tag 1 -1)))))
        
        ;; TODO / DONE
        ;; ("TODO" . ((lambda (tag) (svg-tag-make "TODO" :face 'org-todo
		;; 									           :inverse t :margin 0))))
        ;; ("DONE" . ((lambda (tag) (svg-tag-make "DONE" :face 'org-done :margin 0))))


        ;; Citation of the form [cite:@Knuth:1984] 
        ("\\(\\[cite:@[A-Za-z]+:\\)" . ((lambda (tag)
                                          (svg-tag-make tag
                                                        :inverse t
                                                        :beg 7 :end -1
                                                        :crop-right t))))
        ("\\[cite:@[A-Za-z]+:\\([0-9]+\\]\\)" . ((lambda (tag)
                                                (svg-tag-make tag
                                                              :end -1
                                                              :crop-left t))))

        
        ;; Active date (with or without day name, with or without time)
        (,(format "\\(<%s>\\)" date-re) .
         ((lambda (tag)
            (svg-tag-make tag :beg 1 :end -1 :margin 0))))
        (,(format "\\(<%s \\)%s>" date-re day-time-re) .
         ((lambda (tag)
            (svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0))))
        (,(format "<%s \\(%s>\\)" date-re day-time-re) .
         ((lambda (tag)
            (svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0))))

        ;; Inactive date  (with or without day name, with or without time)
         (,(format "\\(\\[%s\\]\\)" date-re) .
          ((lambda (tag)
             (svg-tag-make tag :beg 1 :end -1 :margin 0 :face 'org-date))))
         (,(format "\\(\\[%s \\)%s\\]" date-re day-time-re) .
          ((lambda (tag)
             (svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0 :face 'org-date))))
         (,(format "\\[%s \\(%s\\]\\)" date-re day-time-re) .
          ((lambda (tag)
             (svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0 :face 'org-date)))))))

(add-hook 'org-mode-hook 'svg-tag-mode)
#+end_src

*** Prettify Tags & Keywords

I have a custom function to prettify tags and other elements, lifted from [[https://github.com/jakebox/jake-emacs/blob/main/jake-emacs/jib-funcs.el][Jake
B's Emacs setup]].

#+begin_src emacs-lisp
(defun soph/prettify-symbols-setup ()
  "Beautify keywords"
  (interactive)
  (setq prettify-symbols-alist
		(mapcan (lambda (x)
                  (when (and (consp x) (stringp (car x)))
                    (list x (cons (upcase (car x)) (cdr x)))))
				'(; Greek symbols
				  ("lambda" . ?λ)
				  ("delta"  . ?Δ)
				  ("gamma"  . ?Γ)
				  ("phi"    . ?φ)
				  ("psi"    . ?ψ)
                  ; Org headers
				  ("#+title:"  . ? )
				  ("#+author:" . ? )
                  ("#+date:"   . ? )                
                  ; Checkboxes
				  ("[ ]" . ?)
				  ("[X]" . ?)
				  ("[-]" . ?)
                  ; Blocks
				  ("#+begin_src"   . ?❯)
				  ("#+end_src"     . ?❯)
                  ("#+results:"   . ?»)
				  ("#+begin_QUOTE" . ?‟)
				  ("#+end_QUOTE"   . ?”)
                  ; Drawers
				  (":properties:" . ?)
                  ; Agenda scheduling
				  ("SCHEDULED:"   . ?🕘)
				  ("DEADLINE:"    . ?⏰)
                  ; Agenda tags  
				  (":@projects:"  . ?☕)
				  (":work:"       . ?🚀)
				  (":@inbox:"     . ?✉)
				  (":goal:"       . ?🎯)
				  (":task:"       . ?📋)
				  (":@thesis:"    . ?📝)
				  (":thesis:"     . ?📝)
				  (":emacs:"      . ?)
				  (":learn:"      . ?🌱)
				  (":code:"       . ?💻)
				  (":fix:"        . ?🛠)
				  (":bug:"        . ?🚩)
				  (":read:"       . ?📚)
                  ; Roam tags
				  ("#+filetags:"  . ?📎)
				  (":wip:"        . ?🏗)
				  (":ct:"         . ?➡)    ; Category Theory
                  (":verb:"       . ?🌐) ; HTTP Requests in Org mode
                  )))
  (prettify-symbols-mode))

(add-hook 'org-mode-hook        #'soph/prettify-symbols-setup)
(add-hook 'org-agenda-mode-hook #'soph/prettify-symbols-setup)
#+end_src

*** Right-Align Tags

Code snippet from [[https://www.reddit.com/r/emacs/comments/185e4k1/comment/kb39xvy/?utm_source=share&utm_medium=web2x&context=3][this Reddit post]]. It actually right-aligns tags, using
font-lock and the display property.

#+begin_src emacs-lisp
(add-to-list 'font-lock-extra-managed-props 'display)
(font-lock-add-keywords 'org-mode
                        `(("^.*?\\( \\)\\(:[[:alnum:]_@#%:]+:\\)$"
                           (1 `(face nil
                                     display (space :align-to (- right ,(org-string-width (match-string 2)) 3)))
                              prepend))) t)
#+end_src

** General Interaction

*** Disable Electric Indent Mode

The built-in electric indent mode is great - just not for Org mode.

#+begin_src emacs-lisp
(add-hook 'org-mode-hook #'(lambda () (electric-indent-local-mode -1)))
#+end_src

*** Opening Links

By default, when opening an Org-link, the current window is split into two. I'd like for the new window to replace the current one. To do this, we need to edit =org-link-frame-setup= and change the default cons =(file . find-file-other-window)= to =(file . find-file)=.

#+begin_src emacs-lisp :tangle no
(setq org-link-frame-setup
      '((vm      . vm-visit-folder-other-frame)
        (vm-imap . vm-visit-imap-folder-other-frame)
        (gnus    . org-gnus-no-new-news)
        (file    . find-file)
        (wl      . wl-other-frame)))
#+end_src

I'd also like to open links with =RET=.

#+begin_src emacs-lisp
(setq org-return-follows-link t)
#+end_src

*** Editing

Don't insert a blank newline before new entries (e.g., list bullets and section headings). I find it annoying when I want to insert a new task under the current one in my agenda if there's a blank newline between the previous entry and the next.

#+begin_src emacs-lisp
(setq org-blank-before-new-entry '((heading . nil)
                                   (plain-list-item . nil)))
#+end_src

** Agenda

First, some regular agenda settings.

I want to open my agenda on the current day, not on any specific weekday.

I also don't want to have a divider line separating my different agenda blocks. This is because I sometimes use packages like Olivetti to center the agenda, which makes the divider line wrap around and take up multiple lines.

Similarly, I right-align my tags, so they also end up shifted around and often on a new line. =org-agenda-remove-tags= doesn't remove them, but for some reason it disables the right-alignment in the agenda, which is perfect.

#+begin_src emacs-lisp
(setq org-agenda-start-on-weekday nil
      org-agenda-block-separator  nil
      org-agenda-remove-tags      t)
#+end_src

*** Super Agenda

[[https://github.com/alphapapa/org-super-agenda][org-super-agenda]] lets you group agenda items into sections, so it's easier to
navigate.

#+begin_src emacs-lisp
(use-package org-super-agenda
  :after org
  :config
  (setq org-super-agenda-header-prefix "\n❯ ")
  ;; Hide the thin width char glyph
  (add-hook 'org-agenda-mode-hook
            #'(lambda () (setq-local nobreak-char-display nil)))
  (org-super-agenda-mode))
#+end_src

*** Org QL

[[https://github.com/alphapapa/org-ql][org-ql]] is a query language for Org mode. It's super powerful and doesn't really belong in the Agenda section of my config, but for now, I only use it to find things and to set up a pretty calendar view.

One of the things I want to find regularly, is a list of all my TODOs marked with the custom state =QUESTION=. Usually, this is stuff that I want to bring up in my next meeting with someone, so it's handy to be able to pull up all the questions I have. =org-ql= is perfect for that.

#+begin_src emacs-lisp :tangle no
(use-package org-ql
  :after org
  :config
  (add-to-list 'org-ql-views
             '("Questions" :buffers-files org-agenda-files :query
               (and
                (not
                 (done))
                (todo "QUESTION"))
               :sort
               (todo priority date)
               :super-groups org-super-agenda-groups :title "Agenda-like")))
#+end_src
*** Agenda Views

With Super Agenda and Org QL, we can now define some display groups for the agenda, to show us exactly the info we want.

We'll set up some groups with the Super Agenda syntax.

#+begin_src emacs-lisp
;; Delete default agenda commands
(setq org-agenda-custom-commands nil)

(defvar regular-view-groups
  '((:name "Scheduled"
     :scheduled t
     :order 1)
	(:name "Deadlines"
     :deadline t
     :order 2)))
#+end_src

Now I'll set up commands to open the day view with =C-c a d= and extended three-day view with =C-c a e=. Notice that I'm first setting some options for the built-in agenda, and then defining a block with Super Agenda groups and Org QL queries.

#+begin_src emacs-lisp
(add-to-list 'org-agenda-custom-commands
	  '("d" "Day View"
		 ((agenda "" ((org-agenda-overriding-header "Day View")
                      (org-agenda-span 'day)
                      (org-super-agenda-groups regular-view-groups)))
		  (org-ql-block '(todo "PROG") ((org-ql-block-header "\n❯ In Progress")))
		  (org-ql-block '(todo "NEXT") ((org-ql-block-header "\n❯ Next Up")))
          (org-ql-block '(todo "WAIT") ((org-ql-block-header "\n❯ Backlog")))
		  (org-ql-block '(priority "A") ((org-ql-block-header "\n❯ Important"))))))


(add-to-list 'org-agenda-custom-commands
		'("e" "Three-Day View"
               ((agenda "" ((org-agenda-span 3)
                            (org-agenda-start-on-weekday nil)
                            (org-deadline-warning-days 0))))))
#+end_src

*** Displaying Scheduled & Deadline Items

Don't show me deadlines or scheduled items if they are done.

#+begin_src emacs-lisp
(setq org-agenda-skip-deadline-if-done  t
	  org-agenda-skip-scheduled-if-done t)
#+end_src

Modify dealine leader text.

#+begin_src emacs-lisp
(setq org-agenda-deadline-leaders '("Deadline:  " "In %2d d.: " "%2d d. ago: "))
#+end_src

** Tasks
*** Task Priorities

Let's increase the number of possible priorities for Org tasks. I'll set
mine to =E= so that we have =A= through =E=, in total five levels.

#+begin_src emacs-lisp
(setq org-lowest-priority  ?F) ;; Gives us priorities A through F
(setq org-default-priority ?E) ;; If an item has no priority, it is considered [#E].

(setq org-priority-faces
      '((65 . "#BF616A")
        (66 . "#EBCB8B")
        (67 . "#B48EAD")
        (68 . "#81A1C1")
        (69 . "#5E81AC")
        (70 . "#4C566A")))
#+end_src

*** Custom TODO States

I'll expand the list of default task states.

#+begin_src emacs-lisp
(setq org-todo-keywords
      '((sequence
         ;; Needs further action
		 "TODO(t)" "PROG(p)" "NEXT(n)" "WAIT(w)" "QUESTION(q) DROP(x)"
		 "|"
         ;; Needs no action currently
		 "DONE(d)")))
#+end_src

*** Mark As Done

Finally, to mark any TODO task, of any state, as DONE quickly, I have a helper
function that I'll bind to =C-c d=.

#+begin_src emacs-lisp
(defun org-mark-as-done ()
  (interactive)
  (save-excursion
    (org-back-to-heading t) ;; Make sure command works even if point is
                            ;; below target heading
    (cond ((looking-at "\*+ TODO")
           (org-todo "DONE"))
		  ((looking-at "\*+ NEXT")
           (org-todo "DONE"))
          ((looking-at "\*+ WAIT")
           (org-todo "DONE"))
		  ((looking-at "\*+ PROG")
           (org-todo "DONE"))
		  ((looking-at "\*+ DROP")
           (org-todo "DONE"))
		  ((looking-at "\*+ DROP")
           (org-todo "QUESTION"))
		  ((looking-at "\*+ DONE")
           (org-todo "DONE"))
          (t (message "Undefined TODO state.")))))
#+end_src

*** Hide DONE Tasks

[[https://github.com/vapniks/hide-lines.git][hide-lines]] is a package that allows us to hide lines in a buffer based on a regexp. I use it to optionally hide elements from my org task files. Sometimes, I'm not ready to archive the task yet, but I want to be able to temporarily hide the tasks marked as "DONE".

#+begin_src emacs-lisp
(use-package hide-lines
  :vc (:url "https://github.com/vapniks/hide-lines.git"
       :branch "master"
       :rev :newest))

(defun hide-done-tasks ()
  (interactive)
  (hide-lines-matching "* DONE"))

(defun show-done-tasks ()
  (interactive)
  (hide-lines-show-all))
#+end_src

*** "Get Things Done" Setup

I'm trying out the Get Things Done method by David Allen, using Nicolas
Rougier's [[https://github.com/rougier/emacs-gtd][GTD configuration]] and Nicolas Petton's [[https://emacs.cafe/emacs/orgmode/gtd/2017/06/30/orgmode-gtd.html][blog post]] on the subject.

The first step is to set the relevant directories.

#+BEGIN_SRC emacs-lisp
(setq org-directory "~/Dropbox/org/")
(add-to-list 'org-agenda-files "inbox.org")
#+END_SRC

Set the archive location to a unified archive.

#+begin_src emacs-lisp
(setq org-archive-location (concat org-directory "archive.org::"))
#+end_src

Then to set up the relevant capture templates, with accompanying keybindings.

#+BEGIN_SRC emacs-lisp
(setq org-capture-templates
       `(("i" "Inbox" entry  (file "inbox.org")
        ,(concat "* TODO %?\n"
                 "/Entered on/ %U"))))
(defun org-capture-inbox ()
     (interactive)
     (call-interactively 'org-store-link)
     (org-capture nil "i"))
#+END_SRC

*** Keybindings

For basic agenda and TODO-related keybindings, I'll use =C-c= followed by a
single, lower-case letter.

#+BEGIN_SRC emacs-lisp
(define-key custom-bindings-map (kbd "C-c l") 'org-store-link)
(define-key custom-bindings-map (kbd "C-c a") 'org-agenda)
(define-key custom-bindings-map (kbd "C-c c") 'org-capture)

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c t") 'org-todo)
  (define-key org-mode-map (kbd "C-c d") 'org-mark-as-done))
#+END_SRC

For whatever reason, I've had an issue with clocking in, where the default
keybinding used =TAB= instead of =C-i= to clock in, so I'll set that manually.

#+begin_src emacs-lisp :tangle no
(define-key org-mode-map (kbd "C-c C-x C-i") 'org-clock-in)
#+end_src

*** Registers

[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Registers.html][Registers]] are easier to access than bookmarks and much more flexible. I'll set
up registers for my GTD files.

#+begin_src emacs-lisp
(set-register ?i (cons 'file (concat org-directory "inbox.org")))
(set-register ?r (cons 'file (concat org-directory "roam/20240128135100-roam.org")))
(set-register ?p (cons 'file (concat org-directory "projects.org")))
(set-register ?c (cons 'file "~/Dropbox/playground/clj-playground/src/clj_playground/playground.clj"))
(set-register ?b (cons 'file "~/Dropbox/projects/blog/org-content/all-posts.org"))
#+end_src

Since I have =C-s= bound to =consult-line= which lets me search everywhere in a
file, I don't really need =C-r= to be bound to the default =isearch-backward=.
Instead, I can use it as the leader key combination to jump to a register.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-r") 'jump-to-register)
#+end_src

** Babel

For working with code blocks in Org mode, I want to make sure code blocks are
not evaluated by default on export. I also want to add some languages.

#+begin_src emacs-lisp
(setq org-export-use-babel       nil
      org-confirm-babel-evaluate nil)
(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (python     . t)
   (haskell    . t)
   (clojure    . t)))
#+end_src

For Python, use whatever interpreter is set by =python-shell-interpreter=.

#+begin_src emacs-lisp
(use-package ob-python
  :ensure nil
  :after (ob python)
  :config
  (setq org-babel-python-command python-shell-interpreter))
#+end_src

** Roam   

[[https://roamresearch.com/][Roam]] is a smart note-taking system in the style of a personal knowledge
management system. [[https://www.orgroam.com/][org-roam]] is a port of this system that uses all plain-text Org-files.

I set up a Roam directory and added a simple configuration for navigating Roam nodes.

#+begin_src emacs-lisp
(use-package org-roam
  :after org
  :hook (org-roam-mode . org-roam-db-autosync-mode)
  :init
  (setq org-roam-v2-ack t)
  :custom
  (org-roam-directory "~/Dropbox/org/roam")
  (org-roam-completion-everywhere t)
  :bind
  ("C-c n t" . org-roam-buffer-toggle)
  ("C-c n f" . org-roam-node-find)
  ("C-c n i" . org-roam-node-insert)
  ("C-c q"   . org-roam-tag-add)
  :config
  ;; Sync my Org Roam database automatically
  (org-roam-db-autosync-mode)
  ;; Open Org files in same window
  (add-to-list 'org-link-frame-setup '(file . find-file)))
#+end_src

*** Consult Org Roam

#+begin_src emacs-lisp
(use-package consult-org-roam
   :ensure t
   :after org-roam
   :init
   (require 'consult-org-roam)
   ;; Activate the minor mode
   (consult-org-roam-mode 1)
   :custom
   ;; Use `ripgrep' for searching with `consult-org-roam-search'
   (consult-org-roam-grep-func #'consult-ripgrep)
   ;; Configure a custom narrow key for `consult-buffer'
   (consult-org-roam-buffer-narrow-key ?r)
   ;; Display org-roam buffers right after non-org-roam buffers
   ;; in consult-buffer (and not down at the bottom)
   (consult-org-roam-buffer-after-buffers t)
   :config
   ;; Eventually suppress previewing for certain functions
   (consult-customize
    consult-org-roam-forward-links
    :preview-key "M-.")
   :bind
   ;; Define some convenient Org Roam keybindings
   ("C-c n e" . consult-org-roam-file-find)
   ("C-c n b" . consult-org-roam-backlinks)
   ("C-c n l" . consult-org-roam-forward-links)
   ("C-c n r" . consult-org-roam-search))
#+end_src

*** Show Tags in Search

When searching for nodes, you can search either by name or by tag. Both are
shown in the menu.

#+begin_src emacs-lisp
(setq org-roam-node-display-template
      (concat "${title:*} "
        (propertize "${tags:10}" 'face 'org-tag)))
#+end_src

*** Graph UI

[[https://github.com/org-roam/org-roam-ui][Org Roam UI]] gives you a pretty and functional graph of your notes, Obsidian-style.

#+begin_src emacs-lisp
(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))
#+end_src

** Hugo

[[https://gohugo.io/][Hugo]] is a static site generator. By default, it uses a Markdown flavour called
Blackfriday. The package [[https://github.com/kaushalmodi/ox-hugo/][ox-hugo]] can export Org files to this format, and
also generate appropriate front-matter. I use it to write my blog in Org and
easily put it online.

#+BEGIN_SRC emacs-lisp
(use-package ox-hugo
  :after org)
#+END_SRC

I've had a great time blogging with =ox-hugo=, but it's a little bothersome to
have to rewrite the front-matter required in the blog post for it to export
property every time, so below is a little snippet lifted from [[https://ox-hugo.scripter.co/doc/org-capture-setup/][ox-hugo's blog]].

The file =all-posts,org= needs to be present in 'org-directory' and the file's
heading must be "Blog Posts". It can even be a symlink pointing to the actual location of all-posts.org! If you've named yours differently, change these values.

#+begin_src emacs-lisp
(with-eval-after-load 'org-capture
  (defun org-hugo-new-subtree-post-capture-template ()
    "Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information."
    (let* ((title (read-from-minibuffer "Post Title: "))
           (fname (org-hugo-slug title)))
      (mapconcat #'identity
                 `(
                   ,(concat "* TODO " title)
                   ":PROPERTIES:"
                   ,(concat ":EXPORT_FILE_NAME: " fname)
                   ":END:"
                   "%?\n")          ;Place the cursor here finally
                 "\n")))

  (add-to-list 'org-capture-templates
               '("h"                ;`org-capture' binding + h
                 "Hugo post"
                 entry
                 (file+olp "all-posts.org" "Blog Posts")
                 (function org-hugo-new-subtree-post-capture-template))))
#+end_src

** Org Present

[[https://github.com/rlister/org-present][org-present]] is a mode for creating straightforward and nice presentations from Org-files. Most of this config is from [[https://systemcrafters.net/emacs-tips/presentations-with-org-present/][System Crafters' blog post]] on the subject.

#+begin_src emacs-lisp
(defun soph/org-present-prepare-slide ()
  ;; Show only top-level headlines
  (org-overview)
  ;; Unfold the current entry
  (org-fold-show-entry)
  ;; Show only direct subheadings of the slide but don't expand them
  (org-fold-show-children))

(defun soph/org-present-start ()
  ;; Tweak font sizes
  (setq-local
   face-remapping-alist '((default (:height 1.5) variable-pitch)
                          (header-line (:height 3.0) variable-pitch)
                          (org-document-title (:height 1.75) org-document-title)
                          (org-block-begin-line (:height 0.7) org-block)))
  ;; Set a blank header line string to create blank space at the top
  (setq header-line-format " "))

(defun soph/org-present-end ()
  ;; Reset font customizations
  (setq-local face-remapping-alist '((default variable-pitch default)))
  ;; Clear the header line string so that it isn't displayed
  (setq header-line-format nil))

(use-package org-present
  :defer t
  :hook
  ((org-present-after-navigate-functions . soph/org-present-prepare-slide)
   (org-present-mode                     . soph/org-present-start)
   (org-present-mode-quit                . soph/org-present-end)))
#+end_src

** Org Conveniencies
*** Pasting Images with =org-download=

[[https://github.com/abo-abo/org-download][org-download]] lets me easily put copied screenshots into my org-documents.

#+begin_src emacs-lisp
(use-package org-download
  :after org
  :bind
  (:map org-mode-map
        (("s-t"   . org-download-screenshot)
         ("s-y"   . org-download-clipboard)
         ("C-M-y" . org-download-clipboard))))
#+end_src

*** Display inline images by default

This option displays inline images in Org mode by default when opening a file. The default setting is to not do so.

#+begin_src emacs-lisp
(setq org-startup-with-inline-images t)
#+end_src

*** TOC in Org Files

[[https://github.com/snosov1/toc-org][toc-org]] creates nice, Markdown compatible tables of content for your Org files.
Perfect for GitHub READMEs.

#+begin_src emacs-lisp
(use-package toc-org
  :after org
  :config
  (add-hook 'org-mode-hook 'toc-org-mode)

  ;; enable in markdown, too
  (add-hook 'markdown-mode-hook 'toc-org-mode))
#+end_src

* Programming
** Preferences & Extras
*** Custom File Endings

For my MSc thesis, I'm implementing a small functional programming language
called Contra. It's pretty similar to Haskell, so using Haskell mode does a
fairly good job of syntax highlighting my =.con=-files.

#+begin_src emacs-lisp
(add-to-list 'auto-mode-alist '("\\.con\\'" . haskell-mode))
#+end_src

*** Language-Specific Commenting

I use =C-ø= to comment/uncomment lines with [[https://github.com/redguardtoo/evil-nerd-commenter][Evil Nerd Commenter]]. It automatically
detects most programming languages and applies appropriate comment style.

#+begin_src emacs-lisp
(use-package evil-nerd-commenter
  :defer t
  :bind (:map custom-bindings-map ("C-ø" . evilnc-comment-or-uncomment-lines)))
#+end_src

*** Subword Mode

[[https://wikemacs.org/wiki/Subword-mode][subword-mode]] lets you work on each subword in camel case words as individual
words. It makes it much easier to delete and mark parts of function and variable
names.

#+begin_src emacs-lisp
(add-hook 'prog-mode-hook 'subword-mode)
#+end_src

*** Electric Pair Mode

[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Matching.html][electric-pair-mode]] is a built-in Emacs mode that will try to insert matching delimiters automatically. It's pretty handy.

#+begin_src emacs-lisp
(electric-pair-mode 1)
#+end_src

*** Highlight TODOs

It's nice to highlight TODOs in the code. [[https://github.com/tarsius/hl-todo][hl-todo]] does just that and lets you specify the faces to use for different TODO keywords. Let's activate it in =prog-mode=.

#+begin_src emacs-lisp
(use-package hl-todo
    :hook (prog-mode . hl-todo-mode)
    :config
    (setq hl-todo-highlight-punctuation ":"
          hl-todo-keyword-faces
          `(("TODO"       font-lock-keyword-face  bold)
            ("FIXME"      error                   bold)
            ("HACK"       font-lock-constant-face bold)
            ("REVIEW"     font-lock-keyword-face  bold)
            ("NOTE"       success                 bold)
            ("DEPRECATED" font-lock-doc-face      bold))))
#+end_src

*** Markdown

Need-to-have for programmers.

#+begin_src emacs-lisp
(use-package markdown-mode
  :defer t)
#+end_src

** Flycheck

[[https://github.com/flycheck/flycheck][Flycheck]] is an on-the-fly syntax checker.

I'll turn off the error messages in the echo area, because they overlap with useful info from LSP mode. I can still see the error, either by hovering over it with the mouse or by pressing =M--= or hovering over it.

After version 37.0, Flycheck's error list shows up at the bottom of the screen by default. I'm a strict two-windows max, side-by-side split kind of person, so I'm overriding that too.

#+begin_src emacs-lisp
(global-unset-key (kbd "M--"))

(use-package flycheck
  :defer t
  :init (global-flycheck-mode)
  :bind (:map custom-bindings-map
              ("M-- -"    . flycheck-explain-error-at-point)
              ("M-- M--"  . flycheck-explain-error-at-point)
              ("M-- ?"    . flycheck-describe-checker)
              ("M-- C"    . flycheck-clear)
              ("M-- C-c"  . flycheck-compile)
              ("M-- C-w"  . flycheck-copy-errors-as-kill)
              ("M-- H"    . display-local-help)
              ("M-- V"    . flycheck-version)
              ("M-- c"    . flycheck-buffer)
              ("M-- e"    . flycheck-explain-error-at-point)
              ("M-- h"    . flycheck-display-error-at-point)
              ("M-- i"    . flycheck-manual)
              ("M-- l"    . flycheck-list-errors)
              ("M-- n"    . flycheck-next-error)
              ("M-- p"    . flycheck-previous-error)
              ("M-- s"    . flycheck-select-checker)
              ("M-- v"    . flycheck-verify-setup)
              ("M-- x"    . flycheck-disable-checker))
  :config
  (setq flycheck-display-errors-function #'ignore
        flycheck-checker-error-threshold 500)
  (add-hook 'flycheck-after-syntax-check-hook #'soph/update-flycheck-modeline)
  (add-hook 'flycheck-mode-hook #'soph/update-flycheck-modeline)
  (add-hook 'after-save-hook
            (lambda ()
              (when (bound-and-true-p flycheck-mode)
                (flycheck-buffer))))
  (add-hook 'window-buffer-change-functions
            (lambda (_)
              (when (bound-and-true-p flycheck-mode)
                (flycheck-buffer))))
  ;; Display error list in existing side buffer
  (setq flycheck-error-list-display-buffer-action
        '((display-buffer-reuse-window) ; reuse window when possible
          (side . right)))              ; show on the right by default
  :custom
  (flycheck-disabled-checkers '(org-lint)))
#+end_src

** Eldoc

[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Programming-Language-Doc.html][Eldoc]] is Emacs' built-in language documentation feature. It will show function documentation as applicable while you're programming.

#+begin_src emacs-lisp
(use-package eldoc
  :defer t
  :config
  (global-eldoc-mode))
#+end_src

** xref

=xref= is Emacs' built-in cross-reference system. It allows you to follow references and builds a reference stack. For each reference you jump to, a new item is added to the stack, and you can go back to where you were before by popping it off the stack. It's super neat.

[[https://github.com/brett-lempereur/consult-xref-stack][consult-xref-stack]] is a package that enhances =xref= with a searchable list of the references you've navigated through. Even neater!

#+begin_src emacs-lisp
(use-package consult-xref-stack
  :vc
  (:url "https://github.com/brett-lempereur/consult-xref-stack" :branch "main")
  :bind (:map custom-bindings-map
              ("C-," . consult-xref-stack-backward)
              ("C-." . consult-xref-stack-forward)))
#+end_src

** HTTP Requests

[[https://github.com/pashky/restclient.el][restclient.el]] lets you run HTTP requests from a static, plain-text query file. As of April 17 2024, it is unfortunately archived.

#+begin_src emacs-lisp
(use-package restclient
  :defer t)
#+end_src

[[https://github.com/federicotdn/verb][verb]] is a package built on the same concept: Write queries in Org mode, send HTTP requests, and view the results pretty-printed in a new buffer.

#+begin_src emacs-lisp
(use-package verb
  :after org
  :config
  (define-key org-mode-map (kbd "C-c C-r") verb-command-map))
#+end_src

** Tree-Sitter

Tree-sitter is already [[https://lists.gnu.org/archive/html/emacs-devel/2022-11/msg01443.html][built into Emacs 29+]], so I don't need to install anything for that specifically, but I don't want to manage grammars and choosing modes manually.

The package [[https://github.com/renzmann/treesit-auto][treesit-auto]] will automatically try to install tree-sitter language grammars for you and use the tree-sitter version of a major mode (like =clojure-ts-mode=) instead of its normal major mode (like =clojure-mode=), and fall back when that's not possible.

#+begin_src emacs-lisp
(use-package treesit-auto
  :custom
  (treesit-auto-install 'prompt)
  (treesit-auto-langs
   '(bash c clojure css go html javascript make markdown python rust toml typescript tsx yaml))
  :config
  (treesit-auto-add-to-auto-mode-alist 'all)
  (global-treesit-auto-mode))
#+end_src

** Structural Editing

[[https://github.com/Fuco1/smartparens][Smartparens]] is a structural editing package that supports many languages out of the box. I've previously used [[https://www.emacswiki.org/emacs/ParEdit][Paredit]] and [[https://github.com/abo-abo/lispy][lispy]], but neither worked particularly well for TypeScript/JavaScript, which I use at work. Once you get used to structural editing, you just can't (or at least don't want to!) live without it, so I'm trying out Smartparens instead.

It's a lot more powerful than =eletric-pair-mode=, because it allows you to manipulate, select, and navigate forms semantically (by symbols or delimiters).

However, I did have just a few functions from lispy that I don't want to live without, among them:
- =lispy-different= - Switch between the opening and closing delimiters.
- =lispy-comment= - Insert the appropriate number of semicolons and automatically adjust the whitespace before them.
- =lispy-clone= - Clone the current sexp on and paste it on the line below (sp has this as well, but I like the behaviour of lispy's version better).
- =lispy-kill= - Kill the sexp after point. I like it better than the sp version.
- =lispy-move-up= - Move sexp up down, while keeping delimiters balanced.
- =lispy-move-down= - Move sexp down a line, while keeping delimiters balanced.

It's a bit insane to pull in lispy just for these functions, but I've really grown attached to these functions and it's my config, so welcome to the wild west!

#+begin_src emacs-lisp
(use-package lispy
  :defer t
  :config
  (setcdr lispy-mode-map nil)
  :commands
  lispy-different
  lispy-comment
  lispy-move-up
  lispy-mode-down
  lispy-clone
  lispy-kill)

(use-package smartparens
  :hook ((prog-mode        . smartparens-mode)
         (smartparens-mode . (lambda () (electric-pair-local-mode -1))))
  :config
  (require 'smartparens-config)
  (setq smartparens-strict-mode t)
  ;; Don't highlight delimiters when created
  (setq sp-highlight-pair-overlay     nil
        sp-highlight-wrap-overlay     nil
        sp-highlight-wrap-tag-overlay nil)
  ;; Be strict with deleting/creating parens
  (smartparens-global-strict-mode     t)
  :bind (:map smartparens-mode-map

              ;; Navigating
              ("C-f"         . sp-forward-sexp)
              ("C-b"         . sp-backward-sexp)
              ("M-A"         . sp-beginning-of-sexp)
              ("M-E"         . sp-end-of-sexp)
              ("C-M-f"       . sp-up-sexp)
              ("C-M-b"       . sp-backward-up-sexp)

              ;; Slurping & barfing
              ("C-<right>"   . sp-forward-slurp-sexp)
              ("C-<left>"    . sp-forward-barf-sexp)
              ("C-M-<right>" . sp-backward-barf-sexp)
              ("C-M-<left>"  . sp-backward-slurp-sexp)

              ;; Manipulating
              ("M-s"         . sp-splice-sexp)
              ("M-r"         . sp-raise-sexp)
              ("M-<up>"      . sp-splice-sexp-killing-backward)
              ("M-<down>"    . sp-splice-sexp-killing-forward)
              ("C-M-t"       . sp-transpose-sexp)
              ("M-?"         . sp-convolute-sexp)
              ("M-S"         . sp-split-sexp)
              ("M-J"         . sp-join-sexp)

              ;; --- Functions from lispy ---
              ;; Navigating
              ("M-d"         . lispy-different)
              ("C-M-<up>"    . lispy-move-up)
              ("C-M-<down>"  . lispy-move-down)

              ;; Manipulating
              ("M-c"         . lispy-clone)
              ("C-k"         . lispy-kill))
  :config
  ;; Unbind, to avoid conflict with my custom-bindings-map
  (define-key smartparens-strict-mode-map [remap kill-whole-line] nil))
#+end_src

** LSP

[[https://github.com/emacs-lsp/lsp-mode][lsp-mode]] is an Emacs client for the Language Server Protocol (LSP). I have LSP mode setup for Clojure and TypeScript.

I disable a few of the default features. If you want to know more about these and how to enable/disable other =lsp-mode= features, there's a handy guide on [[https://emacs-lsp.github.io/lsp-mode/tutorials/how-to-turn-off/][lsp-mode's website]].

#+begin_src emacs-lisp
(defun soph/maybe-start-lsp ()
    (when buffer-file-name
      ;; Don't start LSP mode when I'm looking at downloaded sources, like clojure.core
      (unless (or (string-prefix-p (expand-file-name "~/.m2/") buffer-file-name)
                  (string-prefix-p (expand-file-name "~/.gitlibs/") buffer-file-name))
        (lsp))))

(use-package lsp-mode
  :defer t
  :init
  (setq lsp-use-plists t)
  :hook ((clojure-ts-mode       . soph/maybe-start-lsp)
         (clojurec-mode         . soph/maybe-start-lsp)
         (lsp-mode              . lsp-enable-which-key-integration)
         (typescript-mode       . lsp)
         (tsx-ts-mode           . lsp)
         (typescript-ts-mode    . lsp)
         (web-mode              . lsp))
  :bind (:map lsp-mode-map
              ("M-<return>" . lsp-execute-code-action)
              ("C-M-."      . lsp-find-references)
              ("C-c r"      . lsp-rename))
  :config
  (setq lsp-diagnostics-provider :flycheck
        lsp-completion-provider  :none)       ;; I use corfu
  ;; Disable visual features
  (setq lsp-headerline-breadcrumb-enable nil  ;; No breadcrumbs
        lsp-lens-enable                  nil  ;; No lenses
        lsp-enable-symbol-highlighting   nil  ;; Don't highlight current symbol

        lsp-file-watch-threshold         2000
        lsp-format-buffer-on-save        nil

        ;; Limit raising of the echo area to show docs
        lsp-signature-doc-lines          3)

  (with-eval-after-load 'lsp-modeline
    (set-face-attribute 'lsp-modeline-code-actions-preferred-face nil
                        :inherit 'font-lock-comment-face)
    (set-face-attribute 'lsp-modeline-code-actions-face nil
                        :inherit 'font-lock-comment-face)))
#+end_src

For Clojure in particular, I want to use CIDER's completions first, and LSP completion only as a fallback. Luckily, the team over at Mattilsynet have already figrued out [[https://github.com/magnars/emacsd-reboot/blob/39c6cbf35cf1618d18d2c103b36db504869a9379/packages/setup-lsp-mode.el#L34-L40][a way to do this]]:

#+begin_src emacs-lisp
;; From https://github.com/magnars/emacsd-reboot/blob/39c6cbf35cf1618d18d2c103b36db504869a9379/packages/setup-lsp-mode.el#L34-L40
(defun soph/use-lsp-completion-only-as-fallback ()
  (when (-contains? completion-at-point-functions #'lsp-completion-at-point)
    (remove-hook 'completion-at-point-functions #'tags-completion-at-point-function t)
    (remove-hook 'completion-at-point-functions #'lsp-completion-at-point t)
    (remove-hook 'completion-at-point-functions t t)
    (add-to-list 'completion-at-point-functions #'lsp-completion-at-point t)
    (add-to-list 'completion-at-point-functions t t)))

(add-hook 'lsp-completion-mode-hook 'soph/use-lsp-completion-only-as-fallback)
#+end_src

[[https://github.com/emacs-lsp/lsp-ui][lsp-ui]] is an extension of the UI capabilities of =lsp-mode=.

#+begin_src emacs-lisp
(use-package lsp-ui
  :after lsp-mode
  :config
  (setq lsp-ui-sideline-enable nil
        lsp-ui-doc-enable      nil))
#+end_src

Similarly, there's [[https://github.com/emacs-lsp/lsp-treemacs][lsp-treemacs]] which I use to display a pretty list of references and a function's incoming call hierarchy. As a tree, of course!

#+begin_src emacs-lisp
(use-package lsp-treemacs
  :after lsp-mode
  :bind (:map lsp-mode-map
              ("C-c ." . lsp-treemacs-references)
              ("C-c :" . lsp-treemacs-call-hierarchy)))
#+end_src

There's also a nice mode for language-aware folding called [[https://github.com/gregsexton/origami.el][Origami]]. Then there's the LSP-backed [[https://github.com/emacs-lsp/lsp-origami/][lsp-origami]].

#+begin_src emacs-lisp
(use-package origami
  :defer t
  :bind (:map origami-mode-map
              ("C-z" . origami-toggle-node)))

(use-package lsp-origami
  :after lsp
  :hook (lsp-mode . lsp-origami-try-enable))
#+end_src

[[https://github.com/blahgeek/emacs-lsp-booster][emacs-lsp-booster]] is a wrapper around your LSP server programs. In the README, the authors explain that it helps speed up LSP mode (and Eglot!) by converting JSON directly into elisp bytecode and by separating reading and writing into different threads.

#+begin_src emacs-lisp
(defun lsp-booster--advice-json-parse (old-fn &rest args)
  "Try to parse bytecode instead of json."
  (or
   (when (equal (following-char) ?#)
     (let ((bytecode (read (current-buffer))))
       (when (byte-code-function-p bytecode)
         (funcall bytecode))))
   (apply old-fn args)))
(advice-add (if (progn (require 'json)
                       (fboundp 'json-parse-buffer))
                'json-parse-buffer
              'json-read)
            :around
            #'lsp-booster--advice-json-parse)

(defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
  "Prepend emacs-lsp-booster command to lsp CMD."
  (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)))
(advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command)
#+end_src

** Programming Languages
*** Clojure

I've switched to [[https://github.com/clojure-emacs/clojure-ts-mode][clojure-ts-mode]], which is near feature parity with the original [[https://github.com/clojure-emacs/clojure-mode][clojure-mode]] that I used before.

It has some great [[https://github.com/clojure-emacs/clojure-ts-mode?tab=readme-ov-file#refactoring-support][refactoring features]] under a specific mode map called =clojure-ts-refactor-map=, but they're a little finicky to access with the prefix combination =C-c C-r= folowed by a final command on the form =C-...=.

But luckily, there's a variable called =clojure-ts-refactor-map-prefix= to set this prefix. I've also set a few top-level ones directly in =clojure-ts-mode-map=.

#+begin_src emacs-lisp
(use-package clojure-ts-mode
  :defer t
  :hook ((clojure-ts-mode . whitespace-mode)
         ;; I think the font locking (syntax highlighting) of numbers in clojure-ts-mode
         ;; is ugly, so let's ignore it:
         (clojure-ts-mode . (lambda ()
                              (face-remap-add-relative 'font-lock-number-face 'default))))
  :bind (:map clojure-ts-mode-map
              ("C-("     . clojure-ts-convert-collection-to-list)
              ("C-'"     . clojure-ts-convert-collection-to-quoted-list)
              ("<C-lsb>" . clojure-ts-convert-collection-to-vector)
              ("C-{"     . clojure-ts-convert-collection-to-map)
              ("C-#"     . clojure-ts-convert-collection-to-set)
              (";"       . lispy-comment))
  :config
  (setq clojure-ts-toplevel-inside-comment-form t))
#+end_src

Next, we need [[https://github.com/clojure-emacs/cider][CIDER]], the Clojure Interactive Development Environment that Rocks!

In addition to refactoring keybindings, there are some more keybindings I'd like to add to =cider-mode=.

*cider-selector:*
There's a very handy built-in function called =cider-selector= which lets you type a key to select one of CIDER's buffers, such as the =*cider-error*= and =*cider-scratch*= buffers. But it's bound to the kind of clunky =C-c M-s=, so let's rebind it to the more ergonomic =C-c s=.

Prefix any of the selector commands with =4= to open the buffer in a new window instead of the current one.

*Eval buffer and run all tests in namespace:*
Evaluate the whole namespace and run all its tests in one go!

*Eval and copy to clipboard:*
The wonderful team at the Norwegian Food Authority are Emacs hackers after my own heart. They have some really nice functions for evaluating an sexp after point or top-level form at point and copying it to the clipboard. I want this enhanced experience too!

*Eval def:*
If you've ever def'ed something, you'll know that to pretty print it, you need to either pretty print the form itself, or evaluate the symbol that you def'ed and pretty print its value. In other words, you need to move your cursor to the appropriate place. Again the Norwegian Food Authority team have a handy little function for pretty printing from anywhere within the =def= form!

#+begin_src emacs-lisp
(defun soph/cider-eval-and-test-ns ()
  "Evaluate the current namespace, then run all tests associated with it."
  (interactive)
  (cider-eval-buffer)
  (cider-test-run-ns-tests nil))

;; From https://github.com/magnars/emacsd-reboot/blob/28dcd6e8b0d4f94bcb874fe2c8c3a1f6de4b1bb3/packages/setup-cider.el#L113-L124
(defun soph/cider-eval-to-clipboard ()
  "Evaluate the Clojure form at point and put the result on the clipboard."
  (interactive)
  (let ((form (cider-last-sexp)))
    (cider-nrepl-send-eval-request
     form
     (lambda (response)
       (when (nrepl-dict-get response "value")
         (let ((result (nrepl-dict-get response "value")))
           (kill-new result)
           (message "Result copied to clipboard: %s" result))))
     (cider-current-ns))))

;; From https://github.com/magnars/emacsd-reboot/blob/28dcd6e8b0d4f94bcb874fe2c8c3a1f6de4b1bb3/packages/setup-cider.el#L126-L136
(defun soph/cider-eval-defun-to-clipboard ()
  "Evaluate the current top-level form and copy the result to the clipboard."
  (interactive)
  (cider-nrepl-send-eval-request
   (cider-defun-at-point)
   (lambda (response)
     (when (nrepl-dict-get response "value")
      (let ((result (nrepl-dict-get response "value")))
        (kill-new result)
        (message "Result copied to clipboard: %s" result))))
   (cider-current-ns)))

;; From https://github.com/magnars/emacsd-reboot/blob/e863bb13aab4d284d1648bd6b266c1b6f77109e9/packages/setup-clojure-mode.el#L158-L166
(defun soph/cider-eval-def-symbol ()
  "Evaluate and pretty-print the value of the symbol of the def at point."
  (interactive)
  (save-excursion
    (beginning-of-defun)
    (paredit-forward-down)
    (when (looking-at "def\\(\\w*\\)")
      (paredit-forward 2)
      (cider-pprint-eval-last-sexp))))
#+end_src

And now, finally, to assembly!

CIDER adds support for interactive Clojure programming in Emacs. It provides built-in support for firing up a REPL and looking up documentation and source code, but it also has very Emacs-like shortcuts for expected actions, such as =C-x C-e= to evaluate the s-expression at point.

Let's add some keybindings, including for the functions we defined above.

#+begin_src emacs-lisp
(use-package cider
  :defer t
  :bind (:map cider-repl-mode-map
              ("C-l"   . cider-repl-clear-buffer))
  :bind (:map cider-mode-map
              ("C-c s"     . cider-selector)
              ("C-c t"     . soph/cider-eval-and-test-ns)
              ("C-c C-w"   . soph/cider-eval-to-clipboard)
              ("C-c C-M-w" . soph/cider-eval-defun-to-clipboard)
              ("C-c d"     . soph/cider-eval-def-symbol))
  :config
  (setq cider-repl-display-help-banner       nil
        clojure-toplevel-inside-comment-form t
        cider-download-java-sources          t)
  ; (def-cider-selector-method ?e
  ;   "CIDER result buffer."
  ;   cider-result-buffer)
  )

(add-hook 'cider-repl-mode-hook 'smartparens-mode)
#+end_src

I also use CIDER's scratch buffer quite frequently, but it has its own special mode, which means all my normal refactoring keybindings don't work. This advice makes it enter =clojure-ts-mode= when creating a new scratch buffer:

#+begin_src emacs-lisp
(define-advice cider-scratch--create-buffer (:around (orig-fn name repl) clojure-ts)
  (ignore orig-fn)
  (with-current-buffer (get-buffer-create name)
    (clojure-ts-mode)
    (setq-local sesman-system 'CIDER)
    (local-set-key (kbd "C-j") #'cider-eval-print-last-sexp)
    (cider-scratch--attach repl)
    (cider-scratch--insert-welcome-message)
    (current-buffer)))
#+end_src

 [[https://github.com/clj-kondo/clj-kondo][clj-kondo]] is a linter for Clojure. It even has its own flycheck-mode, [[https://github.com/borkdude/flycheck-clj-kondo][flycheck-clj-kondo]]. We need to install it first.

#+begin_src emacs-lisp
(use-package flycheck-clj-kondo
  :ensure t)
#+end_src

 [[https://github.com/clojure-emacs/clj-refactor.el][clj-refactor]] is a CIDER extension for refactoring.
It can auto-insert the namespace of new files, but LSP mode already does that, so let's disable it.

#+begin_src emacs-lisp
(use-package clj-refactor
  :after clojure-ts-mode
  :hook (clojure-ts-mode . clj-refactor-mode)
  :config
  (setq cljr-add-ns-to-blank-clj-files nil)
  (cljr-add-keybindings-with-prefix "C-<return>"))
#+end_src

[[https://github.com/clojure-emacs/cider-eval-sexp-fu][cider-eval-sexp-fu]] provides small improvements on the default way CIDER evaluates sexpressions. Note that the colour of the flash is changed when switching themes in [[#themes][Themes]], via a hook in =auto-dark=.

#+begin_src emacs-lisp
(use-package eval-sexp-fu
  :after cider)

(use-package cider-eval-sexp-fu
  :after cider)
#+end_src

[[https://github.com/clojure-emacs/sayid/][Sayid]] is a powerful debugger and profiler for Clojure, with a great CIDER plugin.

#+begin_src emacs-lisp
(use-package sayid
  :defer t
  :after cider
  :config
  (with-eval-after-load 'clojure-ts-mode
    (sayid-setup-package)))
#+end_src

[[https://github.com/djblue/portal][Portal]] is a tool for debugging Clojure code. In Clojure, you can "tap" variables, which lets you have a look at their concrete values during execution. Portal lets you tap out stuff to a nice UI where you can collapse and inspect the values. It creates a log of nicely printed stuff.

To use Portal, I have a dev profile called =portal= in my =project.clj= and all I need to do is to jack in with this profile, require Portal, and then go. Let's pull in these snippets borrowed from my colleague [[https://github.com/falcowinkler][Falco Winkler]]. The first lets you interactively choose which profile to jack in with. The second is hardcoded to jack in with the =portal= profile.

#+begin_src emacs-lisp
(defun cider-jack-in-with-profile ()
  (interactive)
  (letrec ((profile (read-string "Enter profile names (,separated): "))
           (lein-params (concat "with-profile +" profile " repl :headless")))
    (message "lein-params set to: %s" lein-params)
    (set-variable 'cider-lein-parameters lein-params)
    (cider-jack-in '())))

(defun cider-jack-in-with-portal-profile ()
  (interactive)
  (set-variable 'cider-lein-parameters "with-profile +portal repl :headless")
  (cider-jack-in '()))
#+end_src

**** Babashka

[[https://babashka.org/][Babashka]] is a scripting runtime for Clojure with super fast startup times.

You can trigger a bunch of useful commands in your project with it, and it plays super nice with [[https://github.com/filipesilva/invoker][Invoker]].

And of course, it's nice to have a way to run Babashka tasks from inside Emacs! This code is from the [[https://github.com/magnars/emacsd-reboot/blob/main/packages/setup-babashka-task-mode.el][Emacs config]] from the team over at Mattilsynet (The Norwegian Food Authority).

#+begin_src emacs-lisp
(defun shorten-path (path)
  "Shortens the file PATH by replacing the home directory with ~."
  (let ((home (expand-file-name "~")))
    (if (string-prefix-p home path)
        (concat "~" (substring path (length home)))
      path)))

(defun babashka-find-tasks ()
  "Find all babashka tasks by running `bb tasks'."
  (let* ((default-directory (projectile-project-root))
         (output (shell-command-to-string "bb tasks 2>/dev/null")))
    (when (and output (not (string-empty-p output)))
      (->> (split-string output "\n" t)
           ;; Skip header line "The following tasks are available:"
           (--filter (not (string-match-p "^The following tasks" it)))
           ;; Parse task names (first word of each line)
           (--map (car (split-string (string-trim it) " " t)))
           ;; Filter out nil/empty
           (--filter (and it (not (string-empty-p it))))))))

(defvar babashka--previous-window-configuration nil)
(defvar babashka--previous-task nil)

(defun babashka-invoke-task (&optional repeat?)
  "Invoke a babashka task interactively.
With REPEAT? non-nil, re-run the previous task without prompting."
  (interactive)
  (let* ((project-root (projectile-project-root))
         (short-dir (shorten-path project-root))
         (default-directory project-root)
         (bb-buffer-name (concat "*Babashka " (projectile-project-name) "*"))
         (prev (if (get-buffer bb-buffer-name)
                   (with-current-buffer bb-buffer-name
                     babashka--previous-window-configuration)
                 (list (current-window-configuration) (point-marker))))
         (tasks (babashka-find-tasks))
         (task (cond
                ;; Repeat previous task
                ((and repeat? (get-buffer bb-buffer-name))
                 (with-current-buffer bb-buffer-name
                   babashka--previous-task))
                ;; No tasks found
                ((null tasks)
                 (user-error "No babashka tasks found in %s" short-dir))
                ;; Prompt user to select
                (t (completing-read (format "bb task in %s: " short-dir)
                                    (--map (concat "bb " it) tasks))))))
    (when task
      (async-shell-command task bb-buffer-name)
      (unless (s-equals? (buffer-name) bb-buffer-name)
        (switch-to-buffer-other-window bb-buffer-name))
      (setq-local babashka--previous-window-configuration prev)
      (setq-local babashka--previous-task task)
      (read-only-mode)
      (local-set-key (kbd "b") 'babashka-invoke-task)
      (local-set-key (kbd "g") (lambda () (interactive) (babashka-invoke-task t)))
      (local-set-key (kbd "q") (lambda ()
                                 (interactive)
                                 (let ((conf babashka--previous-window-configuration))
                                   (kill-buffer)
                                   (when conf (register-val-jump-to conf nil))))))))

(with-eval-after-load 'clojure-ts-mode
  (define-key clojure-ts-mode-map (kbd "M-b") 'babashka-invoke-task))
#+end_src

*** Emacs Lisp

First, we need the holy trinity of Elisp libraries: [[https://github.com/magnars/dash.el][dash]] (lists), [[https://github.com/magnars/s.el][s]] (strings), and [[https://github.com/rejeep/f.el][f]] (files).

#+begin_src emacs-lisp
(use-package dash
  :defer t)

(use-package s
  :defer t)

(use-package f
  :defer t)
#+end_src

Next, let's take a page out of CIDER's book and enhance evaluation of Emacs Lisp by adding [[https://github.com/xiongtx/eros][Eros]] (Evaluation Result OverlayS). Also, I'm so used to the CIDER bindings, so let's bind some CIDER-like bindings for elisp as well.

#+begin_src emacs-lisp
(use-package eros
  :defer t
  :functions
  eros-mode
  eros-eval-defun
  :bind (:map emacs-lisp-mode-map
              ("C-c C-c" . eros-eval-defun)
              ("C-c C-k" . eval-buffer))
  :config
  (eros-mode 1))
#+end_src

*** Scheme

Your favourite flavour of Scheme is probably supported by a version of [[https://github.com/emacsmirror/geiser][Geiser]].
This is for the MIT/GNU flavour:

#+begin_src emacs-lisp
(use-package geiser-mit
  :defer t)
#+end_src

*** Haskell

 For Haskell, I think the regular =haskell-mode= is nice. I'll add =haskell-doc-mode=
 which uses eldoc consistently throughout.

 I also want to use the tool [[https://github.com/ndmitchell/hoogle][Hoogle]] from directly within Emacs to quickly
 look up functions and packages. I've set it up according to the GitHub docs, so
 that =C-c h= opens a prompt and querying the database opens a help buffer inside
 Emacs with the results.

 #+BEGIN_SRC emacs-lisp
 (use-package haskell-mode
   :defer t
   :hook (haskell-mode . haskell-doc-mode)
   :config
   (setq haskell-hoogle-command                  "hoogle"
         haskell-compile-stack-build-command     "stack build"
         haskell-compile-stack-build-alt-command "stack build --pedantic"
         haskell-compile-command                 "stack build")
   :bind (:map haskell-mode-map
               ("C-c C-h" . haskell-hoogle)
               ("C-c C-c" . haskell-compile)))
 #+END_SRC

[[https://github.com/jyp/attrap][attrap]] is a package that ATempts To Repair At Point. I'll bind the function to fix the thing at point to =C-c C-f= for "please, FIX!"

#+begin_src emacs-lisp
(use-package attrap
  :defer t
  :bind ("C-c C-f" . attrap-attrap))
#+end_src

*** Agda

 To install Agda, you need Haskell - stack or cabal - and a few other
 programs. Once those are installed, you can add this to your =init.el=.
 Or you can just let =agda-mode setup= do it for you.

 #+begin_src emacs-lisp :tangle no
 (load-file (let ((coding-system-for-read 'utf-8))
                 (shell-command-to-string "agda-mode locate")))
 #+end_src

*** OCaml

 OCaml s some setup for =ocp-indent=,

 #+begin_src emacs-lisp :tangle no
 (use-package ocp-indent
   :defer t)
 #+end_src

 and for =merlin=.
 
 #+begin_src emacs-lisp
 (let ((opam-share (ignore-errors (car (process-lines "opam" "var" "share")))))
       (when (and opam-share (file-directory-p opam-share))
		;; Register Merlin
		(add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))
		(autoload 'merlin-mode "merlin" nil t nil)
		;; Automatically start it in OCaml buffers
		(add-hook 'tuareg-mode-hook 'merlin-mode t)
		(add-hook 'caml-mode-hook 'merlin-mode t)
		;; Use opam switch to lookup ocamlmerlin binary
		(setq merlin-command 'opam)))
 #+end_src

 Then I want integration with Dune, Merlin, and utop for the full IDE-experience.

 #+begin_src emacs-lisp
 ;; Major mode for OCaml programming
 (use-package tuareg
   :defer t
   :mode (("\\.ocamlinit\\'" . tuareg-mode)))

 ;; Major mode for editing Dune project files
 (use-package dune
   :defer t)

 ;; Merlin provides advanced IDE features
 (use-package merlin
   :defer t
   :config
   (add-hook 'tuareg-mode-hook #'merlin-mode)
   ;; we're using flycheck instead
   (setq merlin-error-after-save nil))

 (use-package merlin-eldoc
   :defer t
   :hook ((tuareg-mode) . merlin-eldoc-setup))

 ;; utop REPL configuration
 (use-package utop
   :defer t
   :config
   (add-hook 'tuareg-mode-hook #'utop-minor-mode))
 #+end_src

*** Python

[[https://github.com/jorgenschaefer/elpy][elpy]] is a Emacs Python Development Environment with batteries included.

#+begin_src emacs-lisp
(use-package elpy
  :defer t
  :init
  (elpy-enable)
  :config
  (setq python-shell-interpreter "python"
        python-shell-interpreter-args "-i"))
#+end_src

#+begin_src emacs-lisp
(use-package blacken
  :after elpy)
#+end_src

*** JavaScript/TypeScript

I use TypeScript at work sometimes, but I don't know a lot about it. Luckily, my colleagues [[https://github.com/mbezjak/dotfiles/blob/6e80e47920f0a5143b7ef89d8d9b1e644b5a20aa/emacs.d/lisp/init.el#L536-L582][Miro]] and [[https://github.com/slipset/emacsd/blob/8f31789dbb516ddd7486acb55f9ea48401267df3/init.el#L135-L167][Erik]] have JS/TS setups in their Emacs configs, so I could just steal from people who know what they're doing.

In addition to this, I have LSP mode for TypeScript, autoformatting via Prettier, and the TypeScript tree-sitter grammar installed.

#+begin_src emacs-lisp
(use-package rjsx-mode
  :defer t
  :mode "components\\/.*\\.js\\'")
#+end_src

Of course, we need code formatting. At work, we use [[https://prettier.io/][Prettier]], so let's pull in an Emacs package for that, like [[https://github.com/prettier/prettier-emacs][pretties-js]].

#+begin_src emacs-lisp
(use-package prettier-js
  :hook ((typescript-ts-mode . prettier-js-mode)
         (tsx-ts-mode . prettier-js-mode)))
#+end_src

In CIDER (for Clojure), I have keybindings to jump to a reference and to jump back. [[https://github.com/js-emacs/xref-js2][xref-js2]] gives me the same capabilities for JS/TS, and with a bit of config, my muscle memory will serve me well in frontend-land as well.

#+begin_src emacs-lisp
(use-package xref-js2
  :defer t
  :config
  (define-key js2-mode-map (kbd "M-.") nil)
  (add-hook 'js2-mode-hook
            (lambda () (add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t)))
  (setq xref-js2-search-program 'rg)
  (define-key js2-mode-map (kbd "M-.") 'xref-find-definitions)
  (define-key js2-mode-map (kbd "M-,") 'xref-pop-marker-stack))
#+end_src

It's also nice to have the same keybindings for running tests in TypeScript as I already do in Clojure via CIDER. [[https://github.com/rymndhng/jest-test-mode][jest-test-mode]] comes with CIDER-style bindings out of the box. From the project's README:

| Binding     | Action                                                           |
|-------------+------------------------------------------------------------------|
| =C-c C-t n=   | Runs tests in the current buffer.                                |
| =C-c C-t p=   | Runs all tests in the project.                                   |
| =C-c C-t t=   | Runs the it/test/describe block at the current point.            |
| =C-c C-t a=   | Re-runs the previous test command.                               |
| =C-c C-t d n= | Runs tests in the current buffer with node debugger.             |
| =C-c C-t d t= | Runs the describe block at the current point with node debugger. |
| =C-c C-t d a= | Re-runs the previous test command with node debugger.            |


#+begin_src emacs-lisp
(use-package jest-test-mode
  :defer t
  :commands jest-test-mode
  :hook (typescript-ts-mode typescript-mode typescript-tsx-mode js-mode)
  :config
  (setq jest-test-command-string "npx %s vitest %s %s"))
#+end_src

It's also nice to have a small util for typechecking:

#+begin_src emacs-lisp
(defun run-typescript-check ()
  (interactive)
  (compile "pnpm check-types"))

(add-hook 'typescript-ts-mode-hook
          (lambda ()
            (local-set-key (kbd "C-c C-t c") 'run-typescript-check)))
#+end_src

* Activating Custom Keybindings
*** Extra Keybindings

Most of my custom keybindings are bound directly in the section with the
relevant package, but here are a few extra ones.

Switch to the other window  =C-x o= \to  =M-o=.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "M-o") 'other-window)
#+end_src

I also have both =C-x b= and =C-x C-b= bound to =consult-buffer=.
Sometimes, though, it's nice to have a dedicated =*Ibuffer*= window. Let's bind it to =C-c b=.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-c b") 'ibuffer)
#+end_src

In my lispy+paredit keybindings, I have =C-f= and =C-b= bound to =forward-= and =backward-sexp=, respectively. That's because I use the arrow keys when I want to move one char at a time, and the bindings =C-f/-b= are so much more ergonomic to use than =C-M-f/-b=. So let's do that everywhere!

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-f") 'forward-sexp)
(define-key custom-bindings-map (kbd "C-b") 'backward-sexp)
#+end_src

I haven't bound super to anything on my work's Mac, so the default =s-u= for reverting buffers doesn't work very well for me. I'll add another binding so I can keep using only control and meta.

 #+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-c u") 'revert-buffer)
 #+end_src

*** Activating the Keymap

Throughout the configuration, I've added bindings to my custom-bindings-map.
The last thing we need to to before we can call it a day, is to define a minor
mode for it and activate that mode. The below code does just that.

#+begin_src emacs-lisp
(define-minor-mode custom-bindings-mode
  "A mode that activates custom keybindings."
  :init-value t
  :keymap custom-bindings-map)
#+end_src

* TODOs
- [ ]  Figure out nice way to search PDFs via pdf-tools (nicer than ISearch, preferrably something consult-related)
- [ ]  Check out [[https://github.com/oantolin/embark][embark]]
- [ ]  Check out [[https://github.com/otavioschwanck/harpoon.el][harpoon]]
- [ ]  Check out [[https://github.com/alphapapa/bufler.el][bufler]]
- [ ]  Configure Magit [[https://github.com/magit/forge][Forge]]
- [ ]  Check out [[https://github.com/kickingvegas/casual-suite][casual-suite]] (especially for IBuffer and Dired)
- [ ]  Check out [[https://github.com/emacscollective/no-littering][no-littering]]
- [ ]  Check out [[https://github.com/ISouthRain/emsg-blame][emsg-blame]]
- [ ]  Check out [[https://github.com/gilbertw1/better-jumper][better-jumper]]
- [ ]  Check out [[https://github.com/projectional-haskell/structured-haskell-mode][Structured Haskell Mode]]
- [ ]  Check out [[https://github.com/casouri/eldoc-box#prettify-typescript-error-message][eldoc-box]] (especially for TS/JS errors)

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

config
configuration
emacs

Contributors

SophieBosio

866 commits

sakh1979

4 commits

SophieBosio/.emacs.d

Sophie's Emacs configuration

Emacs Lisp

108

870 commits

updated Aug 30, 2026

See the code

README


#+TITLE: Sophie's Emacs Configuration
#+AUTHOR: Sophie Bosio
:PROPERTIES:
#+PROPERTY: header-args :tangle yes
#+OPTIONS: toc:2
#+STARTUP: overview
:END:

[[./images/config-screenshot.png]]

* About

This is my attempt at keeping my Emacs configuration organised and readable.

I write all my initialisation code in this document as code blocks and then use
=org-babel-tangle= to extract those code blocks into a separate file. That new,
generated file becomes my =init.el=. This way, I can document my code and explain
my choices to my future self - and to anyone else who might be interested in looking at it. I've stolen the code for doing this and several other tidbits from [[https://github.com/larstvei/dot-emacs/blob/master/init.org][Lars Tveito]].

If you're interested in this approach to writing and sharing your config, it's
called a "literate configuration" and there are lots of great blog posts out
there with inspiration and tips!

I've lifted a lot of code from other people's configurations, including:
- [[https://github.com/larstvei/dot-emacs/blob/master/init.org][Lars Tveito]]
- [[https://github.com/jakebox/jake-emacs][Jacob Boxerman]]
- [[https://github.com/purcell/emacs.d][Steve Purcell]]
- [[https://github.com/rougier][Nicolas Rougier]] (especially his Org setup)

I can heartily recommend checking those out.

You likely do not want to copy my configuration file, since it's full of idiosyncrasies and pretty subjective choices. But I do encourage you to take any bits and pieces that seem interesting, try them out, and incorporate the ones you like into your own config.

* Table of Contents :toc_2_gh:
- [[#about][About]]
- [[#setup][Setup]]
  - [[#prerequisites][Prerequisites]]
  - [[#initel-code][=init.el= Code]]
  - [[#git-tracking--practicalities][Git Tracking & Practicalities]]
  - [[#lexical-binding][Lexical Binding]]
  - [[#tangling][Tangling]]
- [[#start-up][Start-Up]]
  - [[#early-init][Early Init]]
  - [[#garbage-collection][Garbage Collection]]
  - [[#optimisations][Optimisations]]
  - [[#package-manager--package-sources][Package Manager & Package Sources]]
- [[#housekeeping][Housekeeping]]
  - [[#built-in-options][Built-in Options]]
  - [[#gpg-keys][GPG Keys]]
  - [[#local-files][Local Files]]
  - [[#terminal-setup][Terminal Setup]]
- [[#custom-keybindings][Custom Keybindings]]
  - [[#custom-keymap][Custom Keymap]]
  - [[#mac-os-modifier-keys][Mac OS Modifier Keys]]
  - [[#unbind-some-default-keys][Unbind Some Default Keys]]
- [[#visuals][Visuals]]
  - [[#decluttering][Decluttering]]
  - [[#frames--windows][Frames & Windows]]
  - [[#programming-specific-visuals][Programming-Specific Visuals]]
  - [[#fonts][Fonts]]
  - [[#icons--emojis][Icons & Emojis]]
  - [[#themes][Themes]]
  - [[#mode-line][Mode Line]]
  - [[#text-display-modes][Text Display Modes]]
- [[#general-editing][General Editing]]
  - [[#built-in-options-1][Built-In Options]]
  - [[#scrolling][Scrolling]]
  - [[#tabs--indentation][Tabs & Indentation]]
  - [[#kill-ring][Kill Ring]]
  - [[#auto-saving][Auto-Saving]]
  - [[#move-where-i-mean][Move Where I Mean]]
  - [[#text-editing-functions][Text Editing Functions]]
  - [[#crux][CRUX]]
  - [[#make-scripts-executable][Make Scripts Executable]]
- [[#buffers--navigation][Buffers & Navigation]]
  - [[#killing-buffers][Killing Buffers]]
  - [[#splitting-windows][Splitting Windows]]
  - [[#opening-closing--switching-windows][Opening, Closing, & Switching Windows]]
  - [[#projectile][Projectile]]
  - [[#javelin][Javelin]]
  - [[#ibuffer][IBuffer]]
  - [[#dired][Dired]]
- [[#completion][Completion]]
  - [[#vertico][Vertico]]
  - [[#vertico-posframe][Vertico Posframe]]
  - [[#corfu][Corfu]]
  - [[#orderless][Orderless]]
- [[#search][Search]]
  - [[#search-utilities][Search Utilities]]
  - [[#consult][Consult]]
  - [[#imenu-list][Imenu List]]
  - [[#marginalia][Marginalia]]
- [[#misc-packages][Misc. Packages]]
  - [[#version-control-magit--friends][Version Control (Magit & Friends)]]
  - [[#trying-packages][Trying Packages]]
  - [[#snippets][Snippets]]
  - [[#better-help-buffers][Better Help Buffers]]
  - [[#jinx-spellchecker][Jinx Spellchecker]]
  - [[#latex][LaTeX]]
  - [[#pdf-tools][PDF Tools]]
  - [[#epubs][EPUBs]]
  - [[#editor-config][Editor Config]]
  - [[#browser-preference][Browser Preference]]
  - [[#elfeed][Elfeed]]
  - [[#config-profiling][Config Profiling]]
- [[#org][Org]]
  - [[#visuals-1][Visuals]]
  - [[#general-interaction][General Interaction]]
  - [[#agenda][Agenda]]
  - [[#tasks][Tasks]]
  - [[#babel][Babel]]
  - [[#roam][Roam]]
  - [[#hugo][Hugo]]
  - [[#org-present][Org Present]]
  - [[#org-conveniencies][Org Conveniencies]]
- [[#programming][Programming]]
  - [[#preferences--extras][Preferences & Extras]]
  - [[#flycheck][Flycheck]]
  - [[#eldoc][Eldoc]]
  - [[#xref][xref]]
  - [[#http-requests][HTTP Requests]]
  - [[#tree-sitter][Tree-Sitter]]
  - [[#structural-editing][Structural Editing]]
  - [[#lsp][LSP]]
  - [[#programming-languages][Programming Languages]]
- [[#activating-custom-keybindings][Activating Custom Keybindings]]
- [[#todos][TODOs]]

* Setup
** Prerequisites

You probably don't want to run this configuration as-is, since it's highly personal and very likely contains things you don't want in your Emacs.

However, if you do want to try it, or if you want to steal a chunk and something's not working right, this is the software that I have installed in addition to Emacs and that is present in this config, one way or another.

This doubles as a memo to myself for when I need to set up a new machine.

- [[https://www.mozilla.org/en-US/firefox/][Firefox Browser]]
- [[https://github.com/BurntSushi/ripgrep][ripgrep]] search utility
- [[https://github.com/ggreer/the_silver_searcher][ag (The Silver Searcher)]] search utility
- [[https://tree-sitter.github.io/tree-sitter/][Tree-Sitter]] parser generator & incremental parsing library
- [[https://github.com/AbiWord/enchant][Enchant]] spellchecker library & [[https://aspell.net/][GNU Aspell]]
- [[https://www.tug.org/interest.html#free][LaTeX]] type-setting system

Here are the programming languages and utils I set up. The configuration for other languages I have in here shouldn't break anything if you don't have the accompanying software.

- [[https://docs.haskellstack.org/en/stable/][Stack]], [[https://www.haskell.org/cabal/][Cabal]], and [[https://github.com/haskell/haskell-language-server][hsl]] for Haskell - all of which you can install via [[https://www.haskell.org/ghcup/][GHCup]]
- [[https://agda.readthedocs.io/][Agda]], for which you also need Haskell and the Haskell packages [[https://haskell-alex.readthedocs.io/en/][Alex]] and [[https://haskell-happy.readthedocs.io/en/][Happy]]
- [[https://clojure.org/guides/install_clojure][Clojure]], for which you also need a JDK and Clojure runtime, for example the [[https://clojure.org/guides/install_clojure][Clojure CLI tools]] or [[https://leiningen.org/][Leiningen]]

I use these fonts:

- [[https://github.com/weiweihuanghuang/fragment-mono][Fragment Mono]]
- [[https://fonts.google.com/specimen/Open+Sans][Open Sans]]
- [[https://www.nerdfonts.com/font-downloads][Roboto Mono (Nerd Font)]]
- Apple Emoji

The rest of what you need *should* be downloaded by this configuration file. If you try it and find anything missing from this list, please let me know!

** =init.el= Code

As mentioned, I use =org-babel-tangle= and this document, written in Org mode.

The code below extracts the elisp configuration code and creates/overwrites the
=~/.emacs.d/init.el= configuration file when the =.org=-file is saved.
Therefore, changes are only done in the =.org=-file, where writing longer
comments about how things work and why things are added is easier, and then the resulting =init.el=-file remains clean and without excessive comments.

This is what the =init.el= file should look like, prompting it to tangle the =init.org= file and replace itself with that code.

#+BEGIN_SRC emacs-lisp :tangle no
;; We need org in order to make use of the tangling functionality
(require 'org)
;; Open the org-mode configuration
(find-file (concat user-emacs-directory "init.org"))
;; Tangle the file
(org-babel-tangle)
;; Load the tangled file
(load-file (concat user-emacs-directory "init.el"))
;; Byte-compile it
(byte-compile-file (concat user-emacs-directory "init.el"))
#+END_SRC

** Git Tracking & Practicalities

Now we also don't need to track the generated =init.el= file on Git, since it is directly derived from =init.org=.

This code makes Git ignore changes to =init.el=:

#+BEGIN_SRC sh :tangle no
git update-index --assume-unchanged init.el
#+END_SRC

If you do want to start tracking the file again, you can use:

#+BEGIN_SRC sh :tangle no
git update-index --no-assume-unchanged init.el
#+END_SRC

** Lexical Binding

First, I want lexical scoping for the =init=-file, so I will add that to the top of the file.

#+BEGIN_SRC emacs-lisp
;;; -*- lexical-binding: t -*-
#+END_SRC

** Tangling

Now to tangling! The rest of the text and code in this section is lifted directly from Lars' configuration.

The =init.el= should (after the first run) mirror the source blocks in the =init.org=. We can use =C-c C-v t= to run =org-babel-tangle=, which extracts the code blocks from the current file into a source-specific file (in this case a =.el=-file).

To avoid doing this each time a change is made we can add a function to the =after-save-hook= ensuring to always tangle and byte-compile =.org=-document after changes.

#+BEGIN_SRC emacs-lisp
   (defun tangle-init ()
     "If the current buffer is init.org the code-blocks are
   tangled, and the tangled file is compiled."
     (when (equal (buffer-file-name)
                  (expand-file-name (concat user-emacs-directory "init.org")))
       ;; Avoid running hooks when tangling.
       (let ((prog-mode-hook nil))
         (org-babel-tangle)
         (byte-compile-file (concat user-emacs-directory "init.el")))))

   (add-hook 'after-save-hook 'tangle-init)
#+END_SRC

* Start-Up
** Early Init

Emacs 27 introduced =early-init.el=, which is like =init.el= but ran before that, and before the UI and packages are initialised. I've taken code snippets from other configs to put in my =early-init.el= and the blocks in this section tangle to =early-init.el= instead of =init.el=.

In particular, the code below is a combination of code from:
- [[https://github.com/ogdenwebb/snug-emacs/blob/main/early-init.el][ogdenwebb's snug-emacs]]
- [[https://github.com/mnewt/dotemacs/blob/master/early-init.el][mnewt's dotemacsl]]
- [[https://github.com/jamescherti/minimal-emacs.d/blob/main/early-init.el][jamescherti's minimal emacs]]

#+begin_src emacs-lisp :tangle early-init.el
;;; -*- lexical-binding: t -*-

;; Defer garbage collection
(setq gc-cons-percentage 0.6)

;; Change default max size for reading processes (like LSP mode)
(setq read-process-output-max (* 4 1024 1024)) ;; 4mb

(set-language-environment "UTF-8")

;; Set-language-environment sets default-input-method, which is unwanted.
(setq default-input-method nil)

;; Prefer loading newer compiled files
(setq load-prefer-newer t)

;; Prevent the glimpse of un-styled Emacs by disabling these UI elements early.
(setq default-frame-alist
      '((vertical-scroll-bars . nil)
        (menu-bar-lines       . 0)
        (tool-bar-lines       . 0)))

(setq default-frame-scroll-bars 'right)
(setq scroll-bar-mode nil)

;; Resizing the Emacs frame can be a terribly expensive part of changing the
;; font. By inhibiting this, we easily halve startup times with fonts that are
;; larger than the system default.
(setq frame-inhibit-implied-resize t
      frame-resize-pixelwise       t)

;; Font compacting can be very resource-intensive, especially when rendering
;; icon fonts on Windows. This will increase memory usage.
(setq inhibit-compacting-font-caches t)

;; Ignore X resources; its settings would be redundant with the other settings
;; in this file and can conflict with later config (particularly where the
;; cursor color is concerned).
(advice-add #'x-apply-session-resources :override #'ignore)

;; A second, case-insensitive pass over `auto-mode-alist' is time wasted.
;; No second pass of case-insensitive search over auto-mode-alist.
(setq auto-mode-case-fold nil)

;; Disable bidirectional text scanning for a modest performance boost.
(setq-default bidi-display-reordering  'left-to-right
              bidi-paragraph-direction 'left-to-right)

;; Give up some bidirectional functionality for slightly faster re-display.
(setq bidi-inhibit-bpa t)

;; In PGTK, this timeout introduces latency. Reducing it from the default 0.1
;; improves responsiveness of childframes and related packages.
(when (boundp 'pgtk-wait-for-event-timeout)
  (setq pgtk-wait-for-event-timeout 0.001))

;; Disable warnings from the legacy advice API. They aren't useful.
(setq ad-redefinition-action 'accept)

;; Font compacting can be very resource-intensive, especially when rendering
;; icon fonts on Windows. This will increase memory usage.
(setq inhibit-compacting-font-caches t)

;; Unset `file-name-handler-alist' too (temporarily). Every file opened and
;; loaded by Emacs will run through this list to check for a proper handler for
;; the file, but during startup, it won’t need any of them.
(defvar file-name-handler-alist-old file-name-handler-alist)
(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook
          (lambda ()
            (setq file-name-handler-alist file-name-handler-alist-old)))

;; For LSP mode, use plists for deserialization
;; For more info, see https://emacs-lsp.github.io/lsp-mode/page/performance/#use-plists-for-deserialization
(setenv "LSP_USE_PLISTS" "true")

;; Remove "For information about GNU Emacs..." message at startup
(advice-add #'display-startup-echo-area-message :override #'ignore)

;; Suppress the vanilla startup screen completely. Even if disabled with
;; `inhibit-startup-screen', it would still initialize anyway.
(advice-add #'display-startup-screen :override #'ignore)

;; Shave seconds off startup time by starting the scratch buffer in
;; `fundamental-mode'
(setq initial-major-mode 'fundamental-mode
      initial-scratch-message nil)

;; Disable startup screens and messages
(setq inhibit-splash-screen t)
#+end_src

From [[https://emacsnotes.wordpress.com/2022/09/11/three-bonus-keys-c-i-c-m-and-c-for-your-gui-emacs-all-with-zero-headache/][this blog post]] on the blog Emacs Notes, I've also lifted this piece of code that goes into =early-init.el=. Normally, the three keys =C-i=, =C-m= and =C-[= are reserved in GUI Emacs to match expected behaviour in terminal Emacs, and terminal applications generally. They're bound to =TAB=, =RET= and =ESC=, respectively.

But we can unlock them by giving the keys explicit names in =early-init.el= and using the new, named keys in our config if we surround the keybinding with chevrons. So, below I've called =C-i= by the "name" =C-i=. But since it's a name and not a literal key, I need to write =<C-i>= when I want to use the key to define a binding.

#+begin_src emacs-lisp :tangle early-init.el
(add-hook
 'after-make-frame-functions
 (defun setup-blah-keys (frame)
   (with-selected-frame frame
     (when (display-graphic-p) ; don't remove this condition, if you want
                               ; terminal Emacs to be usable
       (define-key input-decode-map (kbd "C-i") [C-i])
       (define-key input-decode-map (kbd "C-[") [C-lsb]) ; left square bracket
       (define-key input-decode-map (kbd "C-m") [C-m])))))
#+end_src

** Garbage Collection

Famously, the Emacs garbage collector can impede startup times quite dramatically. Therefore, a common tweak is to disable the garbage collector during initialisation, and then resetting it afterwards. Luckily, there exists a package exactly for this purpose called the [[https://github.com/emacsmirror/gcmh][Garbage Collector Magic Hack]]-

#+BEGIN_SRC emacs-lisp
(use-package gcmh
  :config
  (gcmh-mode 1))
#+END_SRC

** Optimisations

I found a lot of goodies in [[https://emacsredux.com/blog/2026/04/07/stealing-from-the-best-emacs-configs/][this blog post]] by Bozhidar Batsov about common Emacs config settings.

This prevents Emacs from trying to process right-to-left text (e.g., Arabic).

#+begin_src emacs-lisp
(setq-default bidi-display-reordering 'left-to-right
              bidi-paragraph-direction 'left-to-right)
(setq bidi-inhibit-bpa t)
#+end_src

Skip fontification while I'm typing.

#+begin_src emacs-lisp
(setq redisplay-skip-fontification-on-input t)
#+end_src

I also get quite a lot of compilation warnings, especially from native compilation, but they are usually safe to ignore.

#+begin_src emacs-lisp
(setq native-comp-async-report-warnings-errors 'silent) ;; native-comp warning
(setq byte-compile-warnings '(not free-vars unresolved noruntime lexical make-local))
#+end_src

Disable warnings about obsolete functions when compiling.

#+begin_src emacs-lisp
(eval-when-compile
  (dolist (sym '(cl-flet lisp-complete-symbol))
    (setplist sym (use-package-plist-delete
                   (symbol-plist sym) 'byte-obsolete-info))))
#+end_src

This is an optimisation borrowed from Doom Emacs' =core.el=.

#+begin_src emacs-lisp
(setq which-func-update-delay 1.0)
#+end_src

Prevent Emacs from freezing when updating ELPA.

#+begin_src emacs-lisp
(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")
#+end_src

Make sure we're using the correct native compilation cache.

#+begin_src emacs-lisp
(setq native-comp-eln-load-path '("~/.emacs.d/eln-cache/"))
#+end_src

** Package Manager & Package Sources

To manage downloaded packages, Emacs comes with =package.el= installed. In
addition, I want to use =use-package=, so let's make sure we have those loaded.

#+begin_src emacs-lisp
(require 'package)
(require 'use-package)
(require 'use-package-ensure)
(setq use-package-always-ensure t)
#+end_src

Next, I'll set up my package sources. These are very common and well-maintained mirrors.

#+BEGIN_SRC emacs-lisp
(setq package-archives
      '(("GNU ELPA"     . "https://elpa.gnu.org/packages/")
        ("MELPA"        . "https://melpa.org/packages/")
        ("ORG"          . "https://orgmode.org/elpa/")
        ("MELPA Stable" . "https://stable.melpa.org/packages/")
        ("nongnu"       . "https://elpa.nongnu.org/nongnu/"))
      package-archive-priorities
      '(("GNU ELPA"     . 20)
        ("MELPA"        . 15)
        ("ORG"          . 10)
        ("MELPA Stable" . 5)
        ("nongnu"       . 0)))
(package-initialize)
#+END_SRC

Also, please upgrade even built-in packages.

#+begin_src emacs-lisp
(setq package-install-upgrade-built-in t)
#+end_src

* Housekeeping
** Built-in Options

First, let's set the Emacs user and default directories explicitly:

 #+BEGIN_SRC emacs-lisp
 (setq user-emacs-directory "~/.emacs.d/")
 (setq default-directory "~/")
 #+END_SRC

 Set UFT-8 as preferred coding system.

 #+BEGIN_SRC emacs-lisp
 (set-language-environment    "UTF-8")
 (setq locale-coding-system   'utf-8)
 (prefer-coding-system        'utf-8)
 (set-default-coding-systems  'utf-8)
 (set-terminal-coding-system  'utf-8)
 (set-keyboard-coding-system  'utf-8)
 (set-selection-coding-system 'utf-8)
 #+END_SRC

 Don't warn me when opening files unless over 50 MB.

 #+begin_src emacs-lisp
 (setq large-file-warning-threshold (* 50 1024 1024))
 #+end_src

 From [[https://github.com/jamescherti/minimal-emacs.d/blob/8d975e882026f1433086e6dcf86c1b9acaac61a1/init.el#L159-L173][minimal-emacs.d]], don't create tons of lockfiles or backup files.

 #+begin_src emacs-lisp
 (setq create-lockfiles nil)
 (setq make-backup-files nil)

 (setq backup-directory-alist
       `(("." . ,(expand-file-name "backup" user-emacs-directory))))
 (setq tramp-backup-directory-alist backup-directory-alist)

 (setq backup-by-copying-when-linked t)
 (setq backup-by-copying             t) ; Backup by copying rather renaming
 (setq delete-old-versions           t) ; Delete excess backup versions silently
 (setq version-control               t) ; Use version numbers for backup files
 (setq kept-new-versions             5)
 (setq kept-old-versions             5)
 #+end_src

** GPG Keys

I want to use the standard GPG keyring for Emacs as well.

#+BEGIN_SRC emacs-lisp
(setq package-gnupghome-dir "~/.gnupg")
#+END_SRC

** Local Files

I have a folder with extensions that have been downloaded manually. I'll add these to the =load-path= so Emacs knows where to look for them. My folder is called "local-lisp".

#+BEGIN_SRC emacs-lisp
(defvar local-lisp (concat user-emacs-directory "local-lisp/"))
(add-to-list 'load-path  local-lisp)
(let ((default-directory local-lisp))
  (normal-top-level-add-subdirs-to-load-path))

(defvar local-lisp-themes (concat user-emacs-directory "local-lisp/themes/"))
(add-to-list 'custom-theme-load-path local-lisp-themes)
#+END_SRC

I'll initialise some lists to an empty list. I'll add to the lists both in =private.el= and further down in this file, so it's important that the variable already exists.

#+begin_src emacs-lisp
(setq org-agenda-files '())
(setq register-alist '())
(setq projectile-project-search-path '())
#+end_src

And add custom settings from =custom.el= and private settings from =private.el= if they exist.

#+begin_src emacs-lisp
(setq private-file (expand-file-name "private.el" user-emacs-directory))
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

(when (file-exists-p private-file)
  (load-file private-file))

(when (file-exists-p custom-file)
       (load-file custom-file))
#+end_src

** Terminal Setup

Track current directory in shell.

#+begin_src emacs-lisp
(dirtrack-mode t)
#+end_src

*** Mac OS Environment Variables

On Mac, the environment variables aren't synchronised automatically between the
shell and Emacs.
[[https://github.com/purcell/exec-path-from-shell][exec-path-from-shell]] fixes that.

#+begin_src emacs-lisp
(use-package exec-path-from-shell
  :if (memq window-system '(mac ns))
  :config
  (exec-path-from-shell-initialize))
#+end_src

On Mac, I ran into some trouble with my shell, so I specify the shell as a safeguard against random errors.

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  (setq vterm-shell "/opt/homebrew/bin/fish"))
#+end_src

*** DWIM Shell Commands

 [[https://github.com/xenodium/dwim-shell-command][DWIM Shell Commands]] ("Do What I Mean" shell commands) are a collection of command-line utilities integrated with Emacs. We'll load the optional package with pre-configured commands as well.

 #+begin_src emacs-lisp
 (use-package dwim-shell-command
   :defer t
   :init (require 'dwim-shell-commands))
 #+end_src

* Custom Keybindings
** Custom Keymap

I keep a custom keybinding map that I add to per package, and then activate at
the end of the configuration. This keeps my custom bindings from being
overwritten by extensions' own bindings.

The first step is to create the custom keybinding map. We'll add bindings to it
throughout the config, and then activate it at the end of the config file, at
[[#activating-custom-keybindings][Activating Custom Keybindings]].

#+begin_src emacs-lisp
(defvar custom-bindings-map (make-keymap)
  "A keymap for custom keybindings.")
#+end_src

** Mac OS Modifier Keys

On a Mac, I would want to add some specific settings. As a note to myself, I
have the following settings in Mac OS:

#+begin_src sh :tangle no
caps-lock -> control (ctrl)
control   -> control (ctrl)
option    -> option  (alt)
command   -> command (meta)
#+end_src

#+begin_src emacs-lisp
(setq mac-command-modifier       'meta
      mac-right-command-modifier 'meta
      mac-option-modifier        nil
      mac-right-option-modifier  nil)
#+end_src

** Unbind Some Default Keys

Some of the default keybindings are annoying, so let's unbind them.

I never mean to press =C-x C-z=, which hides the current Emacs frame.

I also don't like using =C-<wheel up/down>= to zoom, which I often do accidentally.

And I never want to suspend the Emacs frame with =C-z=.

#+begin_src emacs-lisp
(global-unset-key (kbd "C-x C-z"))
(global-unset-key (kbd "C-<wheel-up>"))
(global-unset-key (kbd "C-<wheel-down>"))
(global-unset-key (kbd "C-z"))
#+end_src

* Visuals
** Decluttering

Let's declutter a little. This should have gone into =early-init.el=, but I get
strange compilation warnings (optimiser says there's too much on the stack).

#+begin_src emacs-lisp
(dolist (mode
         '(tool-bar-mode       ;; Remove toolbar
           scroll-bar-mode     ;; Remove scollbars
           menu-bar-mode))     ;; Remove menu bar
  (funcall mode 0))
#+end_src

This wouldn't go into =early-init= anyways.

#+begin_src emacs-lisp
(setq inhibit-startup-message           t       ;; No startup message
      inhibit-startup-echo-area-message t       ;; No startup message in echo area
      inhibit-startup-screen            t       ;; No default startup screen
      initial-buffer-choice             t       ;; *scratch* is default startup buffer
      initial-major-mode                'fundamental-mode
      ring-bell-function                'ignore ;; No bell
      display-time-default-load-average nil     ;; Don't show me load time
      scroll-margin                     0       ;; Space between top/bottom
      use-dialog-box                    nil)    ;; Disable dialog
#+end_src

** Frames & Windows
*** Open in Fullscreen

When I open Emacs, I want it to open maximised and fullscreen by default.

#+BEGIN_SRC emacs-lisp
(add-to-list 'default-frame-alist     '(fullscreen . maximized))
;; (add-hook 'window-setup-hook          'toggle-frame-fullscreen t)  ;; F11
#+END_SRC

*** Frame Transparency

This doesn't work ideally, but it does the job. I use it very rarely.

#+begin_src emacs-lisp
 (defun toggle-transparency ()
   (interactive)
   (let ((alpha (frame-parameter nil 'alpha)))
     (set-frame-parameter
      nil 'alpha
      (if (eql (cond ((numberp alpha) alpha)
                     ((numberp (cdr alpha)) (cdr alpha))
                     ;; Also handle undocumented (<active> <inactive>) form.
                     ((numberp (cadr alpha)) (cadr alpha)))
               100)
          '(90 . 55) '(100 . 100)))))
 (global-set-key (kbd "C-c h t") 'toggle-transparency)
#+end_src

*** Frame Border

I want a small border around the whole frame, because I think it looks nicer.

#+BEGIN_SRC emacs-lisp
(add-to-list 'default-frame-alist '(internal-border-width . 16))
#+END_SRC

Some settings to fringes.

#+begin_src emacs-lisp
(set-fringe-mode 10)                          ;; Set fringe width to 10

(setq-default fringes-outside-margins nil)
(setq-default indicate-buffer-boundaries nil) ;; Otherwise shows a corner icon on the edge
(setq-default indicate-empty-lines nil)       ;; Otherwise there are weird fringes on blank lines

(set-face-attribute 'header-line t :inherit 'default)
#+end_src

*** Title Bar on Mac OS

I use [[https://github.com/d12frosted/homebrew-emacs-plus][Emacs Plus]] port for Mac OS. With it, you can get a transparent title bar (i.e., title bar is same colour as theme background) which I think is really nice.

First, install Emacs Plus.

#+begin_src shell :tangle no
# enable tap
brew tap d12frosted/emacs-plus

# install
brew install emacs-plus [options]
#+end_src

Then add the corresponding settings to your =init=-file.

There are two different styles you can choose from. You can have absolutely no title bar on your window or you can have a transparent bar, which still has the three stoplight buttons in the upper-left corner.

For natural title bar, use =ns-transparent-titlebar= and for no title bar, use =undecorated= or =undercorated-round=.

I also set some other options. For example, I don't need info in the title bar about which buffer is in focus, since this info is already in the mode line. I found these options in [[https://notes.alexkehayias.com/emacs-natural-title-bar-with-no-text-in-macos/][this blog post]].

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  ; no title bar
  (add-to-list 'default-frame-alist '(undecorated-round . t))
  ; don't use proxy icon
  (setq ns-use-proxy-icon nil)
  ; don't show buffer name in title bar
  (setq frame-title-format ""))
#+end_src

Finally, in your terminal, run these commands to use transparent title bar and
to hide the icon from the middle of the title bar. I found these in the
aforementioned blog post and in the Emacs-Mac Port's
[[https://github.com/railwaycat/homebrew-emacsmacport/wiki/Natural-Title-Bar][wiki page]] on the subject.

#+begin_src shell :tangle no
# for dark themes
defaults write org.gnu.Emacs TransparentTitleBar DARK

# for light themes
defaults write org.gnu.Emacs TransparentTitleBar LIGHT

# hide document icon from title bar
defaults write org.gnu.Emacs HideDocumentIcon YES
#+end_src

*** Plain Title Bar on GNOME

On GNOME, I can't get a transparent/native title bar. But I can remove the text from the middle, so it's completely plain.

#+begin_src emacs-lisp
(when (eq system-type 'gnu/linux)
  ; don't show buffer name in title bar
  (setq frame-title-format nil)
  ; no title bar
  (add-to-list 'default-frame-alist '(undecorated . t))
  ; add small border to enable drag/resize
  (add-to-list 'default-frame-alist '(drag-internal-border . 1)))
#+end_src

** Programming-Specific Visuals
*** Cursor

I prefer a non-blinking bar cursor (over a block cursor).

#+begin_src emacs-lisp
(blink-cursor-mode        0)
(setq-default cursor-type 'bar)
#+end_src

Also, don't render cursors or regions in non-focused windows.

 #+begin_src emacs-lisp
 (setq-default cursor-in-non-selected-windows nil)
 (setq highlight-nonselected-windows          nil)
 #+end_src

Having a thin cursor can make it hard to see where you are after switching buffers or jumping around. [[https://github.com/Malabarba/beacon][Beacon]] highlights your cursor temporarily, which immediately answers the question "Woah, where am I now?"

#+begin_src emacs-lisp
(use-package beacon
  :defer t
  :init  (beacon-mode 1)
  :bind (:map custom-bindings-map ("C-:" . beacon-blink))
  :config
  (setq beacon-blink-when-window-scrolls nil))
#+end_src

*** Styling Delimiters

When coding, I want my delimiters (parentheses, brackets, etc.) to be colourised
in pairs. [[https://github.com/Fanael/rainbow-delimiters/tree/7919681b0d883502155d5b26e791fec15da6aeca][rainbow-delimiters]] does exactly that.

#+begin_src emacs-lisp
(use-package rainbow-delimiters
  :hook (prog-mode-hook . rainbow-delimiters-mode))
#+end_src

Also, please highlight matching parentheses/delimiters.

#+begin_src emacs-lisp
(show-paren-mode t) ;; Highlight matching parentheses
#+end_src

*** Line Numbers

I usually only need line numbers in programming mode.

#+BEGIN_SRC emacs-lisp
(add-hook 'prog-mode-hook 'display-line-numbers-mode)
#+END_SRC

Also, set the default line number width to 3. This prevents the fringe "jumping" when switching from 2-digit line numbers to 3. I could set it to 4, but I don't want that much space taken up by default, since not all files will be 1000+ lines, while many files are 100+ lines.

#+begin_src emacs-lisp
(setq-default display-line-numbers-width 3)
#+end_src

*** Uniquify Buffers

When opening the files =foo/bar/name= and =baz/bar/name=, use forward slashes to
distinguish them. Default behaviour is angle brackets, which would yield =name<foo/bar>= and =name<baz/bar>=..

#+begin_src emacs-lisp
(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)
#+end_src

*** Highlight Long Lines

Highlight lines over 120 characters long.

#+begin_src emacs-lisp
(setq my-whitespace-style '(face tabs lines-tail)
      whitespace-style my-whitespace-style
      whitespace-line-column 120
      fill-column 120
      whitespace-display-mappings
      '((space-mark 32 [183] [46])
        (newline-mark 10 [36 10])
        (tab-mark 9 [9655 9] [92 9])))

;; in e.g. clojure-mode-hook
;; (whitespace-mode 1)
;; or globally
;; (global-whitespace-mode 1)
(add-hook 'prog-mode 'whitespace-mode)
#+end_src

** Fonts
*** Default, Fixed, and Variable Fonts

Please note that I scale and set Org-specific faces in the [[#visuals-1][Org > Visuals]] section.

For the fixed-pitch font, I'm using the excellent [[https://github.com/weiweihuanghuang/fragment-mono][Fragment Mono]], which
has great ligature support.

I have Open Sans configured as my variable-pitch font.

#+begin_src emacs-lisp
(defvar soph/font-height 115)

(when (eq system-type 'darwin)
  (setq soph/font-height 120))

(when (member "Fragment Mono" (font-family-list))
  (set-face-attribute 'default nil :font "Fragment Mono" :height soph/font-height)
  (set-face-attribute 'fixed-pitch nil :family "Fragment Mono"))

(when (member "Open Sans" (font-family-list))
  (set-face-attribute 'variable-pitch nil :family "Open Sans"))
#+end_src

*** Mixed Pitch Fonts

[[https://gitlab.com/jabranham/mixed-pitch][mixed-pitch]] allows you to mix fixed and variable pitched faces in Org and LaTeX mode.

#+begin_src emacs-lisp
(use-package mixed-pitch
  :defer t
  :hook ((org-mode   . mixed-pitch-mode)
         (LaTeX-mode . mixed-pitch-mode)))
#+end_src

*** Ligatures

The package =ligature.el= provides support for displaying the ligatures of
fonts that already have ligatures. Mine does, and seems to work just fine out
of the box with the ligatures defined on the package's page,

#+begin_src emacs-lisp
(defvar ligature-def '("|||>" "<|||" "<==>" "<!--" "####" "~~>" "***" "||=" "||>"
                       ":::" "::=" "=:=" "===" "==>" "=!=" "=>>" "=<<" "=/=" "!=="
                       "!!." ">=>" ">>=" ">>>" ">>-" ">->" "->>" "-->" "---" "-<<"
                       "<~~" "<~>" "<*>" "<||" "<|>" "<$>" "<==" "<=>" "<=<" "<->"
                       "<--" "<-<" "<<=" "<<-" "<<<" "<+>" "</>" "###" "#_(" "..<"
                       "..." "+++" "/==" "///" "_|_" "www" "&&" "^=" "~~" "~@" "~="
                       "~>" "~-" "**" "*>" "*/" "||" "|}" "|]" "|=" "|>" "|-" "{|"
                       "[|" "]#" "::" ":=" ":>" ":<" "$>" "==" "=>" "!=" "!!" ">:"
                       ">=" ">>" ">-" "-~" "-|" "->" "--" "-<" "<~" "<*" "<|" "<:"
                       "<$" "<=" "<>" "<-" "<<" "<+" "</" "#{" "#[" "#:" "#=" "#!"
                       "##" "#(" "#?" "#_" "%%" ".=" ".-" ".." ".?" "+>" "++" "?:"
                       "?=" "?." "??" ";;" "/*" "/=" "/>" "//" "__" "~~" "(*" "*)"
                       "\\\\" "://"))

(use-package ligature
  :config
  (ligature-set-ligatures 'prog-mode ligature-def)
  (global-ligature-mode t))
#+end_src

*** Zoom

By default, when increasing/decreasing the font size, you only change the font size in the current buffer, so the modeline, other windows, and minbuffer keeps their old text sizes.

I often want to scale *all* the text in the UI when I change text size, so let's create some keybindings.

#+begin_src emacs-lisp
(defun soph/increase-font-size ()
  (interactive)
  (set-face-attribute 'default nil :height (+ (face-attribute 'default :height) 10)))

(defun soph/decrease-font-size ()
  (interactive)
  (set-face-attribute 'default nil :height (- (face-attribute 'default :height) 10)))

(defun soph/reset-font-size ()
  (interactive)
  (set-face-attribute 'default nil :height soph/font-height))

(define-key custom-bindings-map (kbd "C-x C-+") 'soph/increase-font-size)
(define-key custom-bindings-map (kbd "C-x C--") 'soph/decrease-font-size)
(define-key custom-bindings-map (kbd "C-x C-0") 'soph/reset-font-size)
#+end_src

** Icons & Emojis

Add =nerd-icons=.

#+begin_src emacs-lisp
(use-package nerd-icons)
#+end_src

I also want to be able to display emojis with the Apple emoji font. I usually
don't use it, though, so I won't activate the global mode.

#+BEGIN_SRC emacs-lisp
(use-package emojify
  :config
  (when (member "Apple Color Emoji" (font-family-list))
    (set-fontset-font
      t 'symbol (font-spec :family "Apple Color Emoji") nil 'prepend)))
#+END_SRC

** Themes

I really like the =doom-themes= package, in particular their port of the [[https://www.nordtheme.com/][Nord theme]].

#+BEGIN_SRC emacs-lisp
(use-package doom-themes
  :config
  (setq doom-themes-enable-bold t     ; if nil, bold is universally disabled
        doom-themes-enable-italic t)) ; if nil, italics is universally disabled
#+END_SRC

I also have a custom light theme I'm working on called South. Let's pull it down from GitHub.

#+begin_src emacs-lisp
(use-package south-theme
  :vc (:url "https://github.com/SophieBosio/south"
       :rev :newest
       :branch "main"))
#+end_src

And I don't want Emacs to ask me before changing to one of the themes I've used before.

#+begin_src emacs-lisp
(setq custom-safe-themes t)
#+end_src

*** Default Dark & Light Themes

My favourite dark theme is =doom-nord=. I haven't been able to find any light themes I really love, so I made South to act as Nord's bright counterpart. I'll set these two as my default dark and light themes respectively, and load the dark theme by default.

I'll also define a default accent colour, which is used in packages like =eval-sexp-fu=, or wherever I need to define a popping colour outside the theme itself.

#+begin_src emacs-lisp
(defvar soph/default-dark-theme  'doom-nord)
(defvar soph/default-light-theme 'south)

(defvar soph/default-dark-accent-colour  "SkyBlue4")
(defvar soph/default-light-accent-colour "#D9EDFC")

(load-theme soph/default-dark-theme t)
#+end_src

*** Changing Theme With System Theme

[[https://github.com/LionyxML/auto-dark-emacs][auto-dark-emacs]] is a package for switching themes with the system theme. It works both on Linux and on MacOS.

In the hook, I'll set the colour of the =eval-sexp-fu= flash to the =default-{dark/light}-accent-colour=.

For some reason, my light themes leave some fragments that disappear when I load the theme twice, so I'll do that too.

#+begin_src emacs-lisp
(use-package autothemer
  :defer t)

(use-package auto-dark
  :ensure t
  :hook ((auto-dark-dark-mode
          .
          (lambda ()
            (interactive)
            (progn
              (custom-set-faces
               `(eval-sexp-fu-flash
                 ((t (:background
                      ,soph/default-dark-accent-colour)))))
              (load-theme soph/default-dark-theme t))))
         (auto-dark-light-mode
          .
          (lambda ()
            (interactive)
            (progn
              (custom-set-faces
               `(eval-sexp-fu-flash
                 ((t (:background
                      ,soph/default-light-accent-colour)))))
              (load-theme soph/default-light-theme t)))))
  :custom
  (auto-dark-themes                   `((,soph/default-dark-theme) (,soph/default-light-theme)))
  (auto-dark-polling-interval-seconds 5)
  (auto-dark-allow-osascript          t)
  :init (auto-dark-mode t))
#+end_src

We can even change the system theme from within Emacs using a [[https://github.com/xenodium/dwim-shell-command/][dwim-shell-command]] for Mac OS. The Gnome extension [[https://nightthemeswitcher.romainvigier.fr/][Night Theme Switcher]] takes care of things on my Linux machine.

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  (define-key custom-bindings-map (kbd "M-T") 'dwim-shell-commands-macos-toggle-dark-mode))
#+end_src

*** Conflict-Free Theme Changing

When changing themes interactively, as with =M-x load-theme=, the current custom theme is not disabled and this causes some weird issues. For example, the borders around posframes disappear. [[https://github.com/larstvei/dot-emacs?tab=readme-ov-file#advice][This snippet]] from Lars' config advises =load-theme= to always disable the currently enabled themes before switching.

#+begin_src emacs-lisp
(defun disable-custom-themes (theme &optional no-confirm no-enable)
  (mapc 'disable-theme custom-enabled-themes))

(advice-add 'load-theme :before #'disable-custom-themes)
#+end_src

** Mode Line
*** Column Number

Show current column number in mode line.

#+begin_src emacs-lisp
(column-number-mode t) ;; Show current column number in mode line
#+end_src

*** Custom Mode Line

Customising the default mode line is thankfully pretty easy. Note that I use the
[[https://github.com/rainstormstudio/nerd-icons.el][nerd-icons]] package for the VC branch symbol in the code below. I've also
borrowed some code from [[https://amitp.blogspot.com/2011/08/emacs-custom-mode-line.html][this blog post]] by Amit Patel on writing a custom mode
line.

This mode line is heavily inspired by Nicolas Rougier's [[https://github.com/rougier/nano-modeline][Nano Modeline]] and he
even helped me figure out [[https://www.reddit.com/r/emacs/comments/18ktlkg/comment/kdx3aam/?context=3][how to add vertical padding]] to it.

It has this shape:
=[ lambda <filename> <git branch name>               <Flycheck errors> LLLL:CCCC ]=

First, a variable to hold the Flycheck mode line string and a function to calculate it.

#+begin_src emacs-lisp
(defvar soph/flycheck-mode-line-string nil
  "String showing current Flycheck error counts.")

(defun soph/update-flycheck-modeline ()
  "Update the Flycheck modeline string with current error counts."
  (setq soph/flycheck-mode-line-string
        (if (and (bound-and-true-p flycheck-mode)
                 flycheck-current-errors)
            (let* ((error-counts (flycheck-count-errors flycheck-current-errors))
                   (errors 0)
                   (warnings 0)
                   (infos 0)
                   (parts '()))
              ;; Count error types, both native Flycheck errors and ones from lsp-mode
              (dolist (item error-counts)
                (let ((level (symbol-name (car item)))
                      (count (cdr item)))
                  (cond
                   ((or (eq (car item) 'error)
                        (string-prefix-p "lsp-flycheck-error" level))
                    (setq errors (+ errors count)))
                   ((or (eq (car item) 'warning)
                        (string-prefix-p "lsp-flycheck-warning" level))
                    (setq warnings (+ warnings count)))
                   ((or (eq (car item) 'info)
                        (string-prefix-p "lsp-flycheck-info" level))
                    (setq infos (+ infos count))))))
              (when (> infos 0)
                (push (propertize (format "%d" infos) 'face 'success)
                      parts))
              (when (> warnings 0)
                (push (propertize (format "%d" warnings) 'face 'warning)
                      parts))
              (when (> errors 0)
                (push (propertize (format "‍%d" errors) 'face 'error)
                      parts))
              (if parts
                  (concat (propertize "✱ " 'face 'font-lock-comment-face)
                          (string-join (nreverse parts) (propertize " / " 'face 'font-lock-comment-face))
                          (propertize " ✱ " 'face 'font-lock-comment-face))
                (propertize " - " 'face 'font-lock-comment-face)))
          (propertize " - " 'face 'font-lock-comment-face)))
  (force-mode-line-update))
#+end_src

And then the mode line definition itself:

#+begin_src emacs-lisp
(setq-default mode-line-format
  '("%e"
	(:propertize " " display (raise +0.4)) ;; Top padding
	(:propertize " " display (raise -0.4)) ;; Bottom padding

	(:propertize "λ " face font-lock-comment-face)
	mode-line-frame-identification
	mode-line-buffer-identification

	;; Version control info
	(:eval (when-let (vc vc-mode)
			 ;; Use a pretty branch symbol in front of the branch name
			 (list (propertize "   " 'face 'font-lock-comment-face)
                   ;; Truncate branch name to 50 characters
				   (propertize (truncate-string-to-width
                                (substring vc 5) 50)
							   'face 'font-lock-comment-face))))

	;; Add space to align to the right
	(:eval (propertize
			 " " 'display
			 `((space :align-to
					  (-  (+ right right-fringe right-margin)
						 ,(+ 3
                             (string-width (or soph/flycheck-mode-line-string ""))
                             (string-width "%4l:3%c")))))))

    ;; Flycheck error counts
    (:eval (or soph/flycheck-mode-line-string ""))
	
	;; Line and column numbers
	(:propertize "%4l:%c" face mode-line-buffer-id)))
#+end_src

*** Hide Mode Line

[[https://github.com/hlissner/emacs-hide-mode-line][hide-mode-line-mode]] is extracted from Doom Emacs, and does what it says on the tin. It can also be added to hooks to hide the mode line in certain modes. I have it bound to =C-c h m= - mneumonically "User command: Hide Modeline".

#+begin_src emacs-lisp
(use-package hide-mode-line
  :defer t
  :bind (:map custom-bindings-map ("C-c h m" . hide-mode-line-mode)))
#+end_src

** Text Display Modes
*** Olivetti

[[https://github.com/rnkn/olivetti][Olivetti]] is a minor mode for centering text. For convenience, I'll bind it to =C-c o= to activate/deactivate it on the fly.

#+BEGIN_SRC emacs-lisp
(use-package olivetti
  :defer t
  :config
  (setq olivetti-style      t
        olivetti-body-width 100))
#+END_SRC

*** Adaptive Wrap

In addition, I use [[https://elpa.gnu.org/packages/adaptive-wrap.html][adaptive-wrap]] to visually wrap lines.

#+begin_src emacs-lisp
(use-package adaptive-wrap
  :defer t
  :hook (visual-line-mode . adaptive-wrap-prefix-mode))
#+end_src

*** Writeroom Mode

[[https://github.com/joostkremers/writeroom-mode][Writeroom Mode]] gives you a distraction-free writing environment.

#+begin_src emacs-lisp
(use-package writeroom-mode
  :defer t)
#+end_src

*** Focus

 [[https://github.com/larstvei/Focus][Focus]] dims surrounding text in a semantic manner (sentences,
 paragraphs, sections, code blocks, etc.) making it easier to, well,
 focus. I find this especially helpful when editing LaTeX.

 #+begin_src emacs-lisp
 (use-package focus
   :defer t)
 #+end_src

* General Editing
** Built-In Options

#+BEGIN_SRC emacs-lisp
(delete-selection-mode   t) ;; Replace selected text when yanking
(global-so-long-mode     t) ;; Mitigate performance for long lines
(global-visual-line-mode t) ;; Break lines instead of truncating them
(global-auto-revert-mode t) ;; Revert buffers automatically when they change
(recentf-mode            t) ;; Remember recently opened files
(savehist-mode           t) ;; Remember minibuffer prompt history
(save-place-mode         t) ;; Remember last cursor location in file
(repeat-mode             t) ;; Repeat last command without prefix
#+END_SRC

Recenter the window after restoring with =savehist-mode=. Found in [[https://emacsredux.com/blog/2026/04/07/stealing-from-the-best-emacs-configs/][Bozhidar Batsov's blog post 'Stealing from the Best Emacs Config']].

#+begin_src emacs-lisp
(advice-add 'save-place-find-file-hook :after
            (lambda (&rest _)
              (when buffer-file-name (ignore-errors (recenter)))))
#+end_src

#+BEGIN_SRC emacs-lisp
(setq auto-revert-interval                1    ;; Refresh buffers fast
      auto-revert-verbose                 nil  ;; Don't notify me about reverts
      echo-keystrokes                     0.1  ;; Show keystrokes fast
      frame-inhibit-implied-resize        1    ;; Don't resize frame implicitly
      sentence-end-double-space           nil  ;; No double spaces
      recentf-max-saved-items             1000 ;; Show more recent files
      use-short-answers                   t    ;; 'y'/'n' instead of 'yes'/'no' etc.
      history-length                      25   ;; Only save the last 25 minibuffer prompts
      set-mark-command-repeat-pop         t    ;; Pop successive marks with C-<space>
      global-auto-revert-non-file-buffers t)   ;; Revert Dired and other buffers
#+END_SRC

#+BEGIN_SRC emacs-lisp
(setq-default tab-width 4)  ;; Smaller tabs
#+END_SRC

#+begin_src emacs-lisp
;; Don't try to ping hostnames when looking for files with `find-file-at-point`
(setq ffap-machine-p-known 'reject)
#+end_src

** Scrolling

I want scrolling to be a *lot* slower than it is by default.

#+begin_src emacs-lisp
(setq scroll-conservatively            101
      mouse-wheel-follow-mouse         't
      mouse-wheel-progressive-speed    nil
      ;; Scroll 1 line at a time, instead of default 5
      ;; Hold shift to scroll faster and meta to scroll very fast
      mouse-wheel-scroll-amount        '(1 ((shift) . 3) ((meta) . 6)))
#+end_src

And I want it to be smooth!

#+begin_src emacs-lisp
(pixel-scroll-mode)
(pixel-scroll-precision-mode)

(setq mac-redisplay-dont-reset-vscroll t
      mac-mouse-wheel-smooth-scroll    nil)
#+end_src

** Tabs & Indentation

One of the things that drove me the most insane when I first downloaded Emacs,
was the way it deals with indentation.

I want to use spaces instead of tabs. But if I'm working on a project that does
use tabs, I don't want to mess with other people's code, so I've used this
[[https://www.emacswiki.org/emacs/NoTabs][snippet]] from the Emacs Wiki to infer indentation style.

#+begin_src emacs-lisp
(defun infer-indentation-style ()
  "Default to no tabs, but use tabs if already in project"
  (let ((space-count (how-many "^  " (point-min) (point-max)))
        (tab-count   (how-many "^\t" (point-min) (point-max))))
    (if (> space-count tab-count) (setq-default indent-tabs-mode nil))
    (if (> tab-count space-count) (setq-default indent-tabs-mode t))))

(setq-default indent-tabs-mode nil)
(infer-indentation-style)
#+end_src

Set backtab to =indent-rigidly-left=. Then I can easily unindent regions that use
spaces instead of tabs.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "<backtab>") 'indent-rigidly-left)
#+end_src

And finally, make backspace remove the whole tab instead of just deleting one space.

#+begin_src emacs-lisp
(setq backward-delete-char-untabify-method 'hungry)
#+end_src

** Kill Ring

Another thing that bothered me, was how the =backward-kill-word= command
(C-delete/backspace) would delete not only trailing backspaces, but everything
behind it until it had deleted a word. Additionally, this was automatically
added to the kill ring. With this the help of some regexps, it behaves more like normal Ctrl-Backspace.

The code is taken from [[https://emacs.stackexchange.com/a/30404][this]] and [[https://stackoverflow.com/a/12990359][this]] Stack Exchange/Overflow post.

#+BEGIN_SRC emacs-lisp
(defun soph/delete-dont-kill (arg)
  "Delete characters backward until encountering the beginning of a word.
   With argument ARG, do this that many times. Don't add to kill ring."
  (interactive "p")
  (delete-region (point) (progn (backward-word arg) (point))))

(defun soph/backward-delete ()
  "Delete a word, a character, or whitespace."
  (interactive)
  (cond
   ;; If you see a word, delete all of it
   ((looking-back (rx (char word)) 1)
    (soph/delete-dont-kill 1))
   ;; If you see a single whitespace and a word, delete both together
   ((looking-back (rx (seq (char word) (= 1 blank))) 1)
	(soph/delete-dont-kill 1))
   ;; If you see several whitespaces, delete them until the next word
   ((looking-back (rx (char blank)) 1)
    (delete-horizontal-space t))
   ;; If you see a single non-word character, delete that
   (t
    (backward-delete-char-untabify 1))))
#+END_SRC

Let's bind this in my custom keybindings map.

#+begin_src emacs-lisp
(define-key custom-bindings-map [C-backspace] 'soph/backward-delete)
#+end_src

Speaking of killing text, it's nice to be able to browse the kill ring.

#+begin_src emacs-lisp
(use-package browse-kill-ring
  :defer t)
#+end_src

And I don't need any duplicates in that kill ring.

#+begin_src emacs-lisp
(setq kill-do-not-save-duplicates t)
#+end_src

I also want to save the contents of my clipboard (copied in other programs) before I kill something in Emacs, so it doesn't get overwritten.

#+begin_src emacs-lisp
(setq save-interprogram-paste-before-kill t)
#+end_src

And finally, please persist the kill ring between sessions.

#+begin_src emacs-lisp
(setq savehist-additional-variables
      '(search-ring regexp-search-ring kill-ring))

;; Drop fontifcation of kill ring entries
(add-hook 'savehist-save-hook
          (lambda ()
            (setq kill-ring
                  (mapcar #'substring-no-properties
                          (cl-remove-if-not #'stringp kill-ring)))))
#+end_src

** Auto-Saving

To avoid clutter, let's put all the auto-saved files into one and the same directory.

#+BEGIN_SRC emacs-lisp
(defvar emacs-autosave-directory
  (concat user-emacs-directory "autosaves/")
  "This variable dictates where to put auto saves. It is set to a
  directory called autosaves located wherever your .emacs.d/ is
  located.")

;; Sets all files to be backed up and auto saved in a single directory.
(setq backup-directory-alist
      `((".*" . ,emacs-autosave-directory))
      auto-save-file-name-transforms
      `((".*" ,emacs-autosave-directory t)))
#+END_SRC

I prefer having my files save automatically. Any changes I don't want, I just
don't commit to git. I use =auto-save-buffers-enhanced= to automatically save all
buffers, not just the ones I have open.

But since saving this file - the =init.org=-file - triggers recompilation of
=init.el=, it's really annoying if this file is autosaved when I write to it.
Therefore, I'll disable automatic saving for this file in particular.

Normally, I have auto-saving on for programming, but it's super annoying when writing JS/TS because it keeps reformatting my code as I type. So let's turn it off and save manually instead.

#+BEGIN_SRC emacs-lisp
(use-package auto-save-buffers-enhanced
  :ensure t
  :config
  (auto-save-buffers-enhanced t)
  (setq auto-save-buffers-enhanced-exclude-regexps
        '("init.org"
          "\\(\\.js\\|\\.ts\\|\\.jsx\\|\\.tsx\\|\\.html\\)$")))
#+END_SRC

** Move Where I Mean

[[https://github.com/alezost/mwim.el][mwim]] (Move Where I Mean) takes semantics and indentation into account. This lets us rebind =C-a= and =C-e= to move to the beginning and end of a line while respecting indentation. I.e., don't move to the actual beginning of the line, but to indentation.

#+begin_src emacs-lisp
(use-package mwim
  :ensure t
  :bind (:map custom-bindings-map
              ("C-a" . mwim-beginning-of-code-or-line)
              ("C-e" . mwim-end-of-code-or-line)))
#+end_src

** Text Editing Functions
*** Expand Region

[[https://github.com/magnars/expand-region.el][expand-region]] expand the region (selected text) with semantic units (e.g.,
symbol, word, sentence, paragraph). It's super handy!

=M-q= is bound to =fill-paragraph=. I don't use that binding, but you might want to bind this to a different key combo if you do.

#+begin_src emacs-lisp
(use-package expand-region
  :defer t
  :bind (:map custom-bindings-map
              ("M-q" . er/expand-region)
              ("M-'" . er/contract-region)))
#+end_src

*** Filling/Unfilling

In Emacs, paragraphs can be padded by a bunch of newlines, meaning a what looks like a normal paragraph in Emacs (one line) is actually several lines with =\n= all over. This function removes those and makes the selected region one line again.

#+begin_src emacs-lisp
;;; Stefan Monnier <foo at acm.org>. It is the opposite of fill-paragraph
(defun unfill-paragraph (&optional region)
  "Takes a multi-line paragraph and makes it into a single line of text."
  (interactive (progn (barf-if-buffer-read-only) '(t)))
  (let ((fill-column (point-max))
		;; This would override `fill-column' if it's an integer.
		(emacs-lisp-docstring-fill-column t))
	(fill-paragraph nil region)))
;; Handy key definition
(define-key custom-bindings-map (kbd "C-c n q") 'unfill-paragraph)
#+end_src

*** Multiple Cursors & Symbol Overlay

[[https://github.com/magnars/multiple-cursors.el][multiple-cursors]] makes life so much easier! I often use it to create several cursors directly above one another. I'll trust myself to wield this power responsibly and set the variable =mc/always-run-for-all= to =t=, which disables the default behaviour prompting the user for confirmation when trying to do certain things with the multiple cursors.

#+begin_src emacs-lisp
(use-package multiple-cursors
  :defer t
  :functions
  mc/remove-fake-cursors
  mc/save-excursion
  mc/create-fake-cursor-at-point
  mc/maybe-multiple-cursors-mode
  :bind (:map custom-bindings-map
              ("M-n" . mc/mark-next-like-this)
              ("M-p" . mc/mark-previous-like-this))
  :config
  (setq mc/always-run-for-all t))
#+end_src

[[https://github.com/wolray/symbol-overlay][symbol-overlay]] highlights all occurrences of the symbol at point and allows to jump between them.

#+begin_src emacs-lisp
(use-package symbol-overlay
  :defer t
  :functions
  symbol-overlay-put
  symbol-overlay-mode
  :hook (prog-mode . symbol-overlay-mode)
  :bind (:map custom-bindings-map
              ("C-;" . symbol-overlay-put)
              ("M-N" . symbol-overlay-jump-next)
              ("M-P" . symbol-overlay-jump-prev)))
#+end_src

[[https://xenodium.com/its-all-up-for-grabs-and-it-compounds/][In his blog post]], Alvaro Ramirez (AKA Xenodium) demonstrates one of the best things in Emacs: Seeing things that are almost the way you want them and tweaking them with Elisp so they become that. He takes multiple-cursors and symbol-overlay and combines them. and Ramirez wrote a function that lets symbol-overlay communicate to multiple-cursors that this is where you should give me cursors. Edit all the things at once!  I think it's great, so let's use it and bind it to =C-;=.

#+begin_src emacs-lisp
(defun ar/mc-mark-all-symbol-overlays ()
  "Mark all symbol overlays using multiple cursors."
  (interactive)
  (mc/remove-fake-cursors)
  (when-let* ((overlays (symbol-overlay-get-list 0))
              (point (point))
              (point-overlay (seq-find
                              (lambda (overlay)
                                (and (<= (overlay-start overlay) point)
                                     (<= point (overlay-end overlay))))
                              overlays))
              (offset (- point (overlay-start point-overlay))))
    (setq deactivate-mark t)
    (mapc (lambda (overlay)
            (unless (eq overlay point-overlay)
              (mc/save-excursion
               (goto-char (+ (overlay-start overlay) offset))
               (mc/create-fake-cursor-at-point))))
          overlays)
    (mc/maybe-multiple-cursors-mode)))

(define-key custom-bindings-map (kbd "C-M-;") 'ar/mc-mark-all-symbol-overlays)
#+end_src

*** Undo/Redo

The default "undo until you can redo" behaviour of Emacs still trips me up.
[[https://github.com/emacsmirror/undo-fu][undo-fu]] lets me specify keys to "only undo" or "only redo".

#+begin_src emacs-lisp
(use-package undo-fu
  :defer t
  :bind (:map custom-bindings-map
              ("C-_" . undo-fu-only-undo)
              ("M-_" . undo-fu-only-redo)))
#+end_src

*** Move Lines

[[https://github.com/wyuenho/move-dup][move-dup]] provides bindings for moving and duplicating whole lines. It's super convenient.

#+begin_src emacs-lisp
(use-package move-dup
  :bind (:map custom-bindings-map
              (("C-S-<up>"    . move-dup-move-lines-up)
               ("C-S-<down>"  . move-dup-move-lines-down)
               ("M-W"         . move-dup-duplicate-down))))
#+end_src

*** Join Lines

The normal =join-line= joins the current line with the line above it.

=C-j= is normally bound to =electric-newline-and-maybe-indent= but I use =electric-indent-mode= so I have that behaviour when creating newlines by default, so I feel okay rebinding it.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-j") 'join-line)
#+end_src

In [[http://whattheemacsd.com/key-bindings.el-03.html][What the .emacs.d!?]], Magnar shows that passing =-1= as the argument to =join-line= changes which line you join with which. This joins the current line with the one /below/ it instead of the one above.

#+begin_src emacs-lisp
(define-key custom-bindings-map
            (kbd "M-j")
            (lambda ()
              (interactive)
              (join-line -1)))
#+end_src

*** Kill Whole Line

#+begin_src emacs-lisp
(unbind-key "C-S-k")
(define-key custom-bindings-map (kbd "C-S-k") 'kill-whole-line)
#+end_src

** CRUX

[[https://github.com/bbatsov/crux][CRUX]] is a Collection of Ridiculously Useful eXtensions for Emacs. It has a whole bunch of commands and I'd recommend looking into all the things it supports.

#+begin_src emacs-lisp
(use-package crux
  :defer t
  :bind (:map custom-bindings-map
         ("C-S-<return>" . crux-smart-open-line-above)
         ("M-S-<down>"   . crux-duplicate-current-line-or-region)))
#+end_src

** Make Scripts Executable

When creating a script, automatically do a =chmod +x= so it becomes executable.

#+begin_src emacs-lisp
(add-hook 'after-save-hook
          #'executable-make-buffer-file-executable-if-script-p)
#+end_src

* Buffers & Navigation
** Killing Buffers

Sometimes, I'm putting some work away and I don't want those files to show up in
the buffer list. Killing a buffer with =C-x k= or marking several buffers in the
buffer list to kill them is fine, but can be a bit cumbersome.

I found this function in a [[https://superuser.com/questions/895920/how-can-i-close-all-buffers-in-emacs][Stack Exchange answer]]. It allows me to close the
current buffer easily by pressing =C-c k=. If I prefix it, by writing =C-u C-c k=, then
all "interesting" buffers are killed, leaving internal Emacs buffers intact.
This cleans up all the buffers I've opened or used myself.

#+begin_src emacs-lisp
(defun soph/kill-buffer (&optional arg)
"When called with a prefix argument -- i.e., C-u -- kill all interesting
buffers -- i.e., all buffers without a leading space in the buffer-name.
When called without a prefix argument, kill just the current buffer
-- i.e., interesting or uninteresting."
(interactive "P")
  (cond
    ((and (consp arg) (equal arg '(4)))
      (mapc
        (lambda (x)
          (let ((name (buffer-name x)))
            (unless (eq ?\s (aref name 0))
              (kill-buffer x))))
        (buffer-list)))
    (t
      (kill-buffer (current-buffer)))))

(define-key custom-bindings-map (kbd "C-c k") 'soph/kill-buffer)
#+end_src

** Splitting Windows

I want maximum two windows by default. I have a function, taken from [[https://emacs.stackexchange.com/a/40517][this Stack
Overflow post]], that rewrites the =split-window-sensibly= function to reverse its
preference and essentially prefer splitting side-by-side.

#+begin_src emacs-lisp
(defun split-window-sensibly-prefer-horizontal (&optional window)
"Based on `split-window-sensibly', but prefers to split WINDOW side-by-side."
  (let ((window (or window (selected-window))))
    (or (and (window-splittable-p window t)
         ;; Split window horizontally
         (with-selected-window window
           (split-window-right)))
    (and (window-splittable-p window)
         ;; Split window vertically
         (with-selected-window window
           (split-window-below)))
    (and
         ;; If WINDOW is the only usable window on its frame (it is
         ;; the only one or, not being the only one, all the other
         ;; ones are dedicated) and is not the minibuffer window, try
         ;; to split it horizontally disregarding the value of
         ;; `split-height-threshold'.
         (let ((frame (window-frame window)))
           (or
            (eq window (frame-root-window frame))
            (catch 'done
              (walk-window-tree (lambda (w)
                                  (unless (or (eq w window)
                                              (window-dedicated-p w))
                                    (throw 'done nil)))
                                frame)
              t)))
     (not (window-minibuffer-p window))
     (let ((split-width-threshold 0))
       (when (window-splittable-p window t)
         (with-selected-window window
               (split-window-right))))))))

(defun split-window-really-sensibly (&optional window)
  (let ((window (or window (selected-window))))
    (if (> (window-total-width window) (* 2 (window-total-height window)))
        (with-selected-window window (split-window-sensibly-prefer-horizontal window))
      (with-selected-window window (split-window-sensibly window)))))

(setq split-window-preferred-function 'split-window-really-sensibly)
#+end_src

If I have already split the frame into
two windows and then call a function that opens a new window (for example a
Magit or a compilation buffer), then I want Emacs to reuse the inactive window
instead of creating a new one. Setting both =split-height-threshold= and
=split-width-threshold= to =nil= seems to ensure this.

#+begin_src emacs-lisp
(setq-default split-height-threshold nil
              split-width-threshold  nil
              fill-column            80) ;; Maximum line width
              ;; window-min-width       80) ;; No smaller windows than this
#+end_src

** Opening, Closing, & Switching Windows

Opening, switching and deleting windows becomes super easy with [[https://github.com/dimitri/switch-window][switch-window]].

#+begin_src emacs-lisp :tangle no
(use-package switch-window
  :bind (:map custom-bindings-map
              ("C-x o" . 'switch-window)
              ("C-x 1" . 'switch-window-then-maximize)
              ("C-x 2" . 'switch-window-then-split-below)
              ("C-x 3" . 'switch-window-then-split-right)
              ("C-x 0" . 'switch-window-then-delete)))
#+end_src

Automatically resize windows to keep them proportional.

#+begin_src emacs-lisp
(setq window-combination-resize t)
#+end_src

And [[https://www.emacswiki.org/emacs/TransposeFrame][Transpose
Frame]] has some nice functions for shifting windows around. I only really use the
one to swap the left- and right-hand sides of the frame, but there are
others you might find useful.

#+begin_src emacs-lisp
(use-package transpose-frame
  :bind (:map custom-bindings-map
              ("C-c f" . 'flop-frame)))
#+end_src

** Projectile
 
[[https://projectile.mx/][Projectile]] provides a convenient project interaction interface. I keep most of
my projects in a specific folder, so I'll set Projectile to check that path specifically.

#+begin_src emacs-lisp
(use-package projectile
  :defer t
  :bind (:map custom-bindings-map
              ("C-c p" . projectile-command-map)
              ("C-p"   . projectile-command-map))
  :config
  (add-to-list 'projectile-project-search-path "~/Dropbox/projects/")
  (add-to-list 'projectile-project-search-path "~/Dropbox/playground/")
  (setq projectile-create-missing-test-files t)
  :init
  (projectile-mode))
#+end_src

** Javelin

[[https://github.com/DamianB-BitFlipper/javelin.el][Javelin]] is inspired by ThePrimeagen's [[https://github.com/ThePrimeagen/harpoon][Harpoon]] for NeoVim.

It gives you the ability to bookmark files and buffers, scoped to projects and even Git branches.

By default, =M-<number>= will assign a buffer to that binding if not already bookmarked, and jump to the bookmark if already bound.

#+begin_src emacs-lisp
(use-package javelin
  :ensure t
  :bind (:map javelin-minor-mode-map
              ("M--" . nil)
              ("M-+" . javelin-toggle-quick-menu))
  :init
  (global-javelin-minor-mode))
#+end_src

** IBuffer

=ibuffer= is built-in to Emacs. It shows a list of open buffers and lets you perform lots of useful actions, like sorting and killing buffers.

I was inspired by [[https://tech.tonyballantyne.com/2020/09/26/ibuffer-changed-my-life/][this blog post]] by Tony Ballantyne to tidy up the default =ibuffer= view.

I'd like to tidy it up a bit by organising the buffers by major mode.

#+begin_src emacs-lisp
(setq ibuffer-saved-filter-groups
      '(("default"
         ("Programming"   (predicate . (derived-mode-p 'prog-mode)))
         ("Clojure/CIDER" (or (name  . "clojure")
                              (name  . "cider")
                              (name  . "nrepl")))
         ("Org"           (mode . org-mode))
         ("Dired"         (mode . dired-mode))
         ("Magit"         (name . "magit")))))

(add-hook 'ibuffer-mode-hook
          (lambda ()
            (ibuffer-switch-to-saved-filter-groups "default")))
#+end_src

 I also exclude certain system buffers.

#+begin_src emacs-lisp
(setq ibuffer-never-show-predicates
      '(;; System buffers
        "^\\*Messages\\*$"
        "^\\*scratch\\*$"
        "^\\*Completions\\*$"
        "^\\*Help\\*$"
        "^\\*Apropos\\*$"
        "^\\*info\\*$"
        "^\\*Async-native-compile-log\\*$"

        ;; LSP Buffers
        "^\\*lsp-log\\*$"
        "^\\*clojure-lsp\\*$"
        "^\\*clojure-lsp::stderr\\*$"
        "^\\*ts-ls\\*$"
        "^\\*ts-ls::stderr\\*$"))
#+end_src

And finally, I'll remove some columns I don't use.

#+begin_src emacs-lisp
(setq ibuffer-formats
      '((mark " " (name 60 -1 :left))))
#+end_src

** Dired

Emacs's default file manager is nice, but contains a bit more info than I usually need. =dired-hide-details-mode= does what it says on the tin, and I can easily activate/deactivate it on the fly with the default keybinding, =(=.

I'll also bind a few convenience keys. =C-= followed by an arrow moves into a directory/open a file or move up a directory. And lowercase =c= creates/touches a new file and prompts for a name.

The last line is a setting for MacOS telling it to use =gls= when using dired.

#+begin_src emacs-lisp
(use-package dired
  :ensure nil
  :hook (dired-mode . dired-hide-details-mode)
  :bind (:map dired-mode-map
              ("C-<right>" . dired-find-alternate-file)
              ("C-<left>"  . dired-up-directory)
              ("C-<down>"  . dired-find-alternate-file)
              ("C-<up>"    . dired-up-directory)
              ("c"         . dired-create-empty-file))
  :config
  (when (and (eq system-type 'darwin) (executable-find "gls"))
    (setq dired-use-ls-dired nil)))
#+end_src

From [[https://stackoverflow.com/a/55235833][this StackOverflow post]].

#+begin_src emacs-lisp
(put 'dired-find-alternate-file 'disabled nil) ; disables warning
(define-key dired-mode-map (kbd "RET") 'dired-find-alternate-file) ; was dired-advertised-find-file
(define-key dired-mode-map (kbd "^") (lambda () (interactive) (find-alternate-file "..")))  ; was dired-up-directory
#+end_src

* Completion

Emacs distinguishes between two different kinds of completion: complete-at-point  (text/code autocomlete) and completing-read (completion of Emacs commands, file names, etc.).

For completing-read, I use [[https://github.com/minad/vertico][Vertico]] and for completion-at-point at use [[https://company-mode.github.io/][Company]]. I also use a few complimentary packages that enhance the experience.

** Vertico

[[https://github.com/minad/vertico][Vertico]] is heart of this completion UI!

I'll use the function from [[http://whattheemacsd.com/setup-ido.el-02.html][this What the .emacs.d!? post]] which lets me type =~= at the Vertico prompt to go directly to the home directory. For use with Vertico, I add a call to =delete-minibuffer-contents= so that old path is cleared before starting the new file path (starting at =~/=).

#+begin_src emacs-lisp
(defun soph/take-me-home ()
  (interactive)
  (if (looking-back "/" nil)
      (progn (call-interactively 'delete-minibuffer-contents) (insert "~/"))
    (call-interactively 'self-insert-command)))

(use-package vertico
  :defer t
  :bind (:map vertico-map ("~" . soph/take-me-home))
  :config
  (vertico-mode)
  (vertico-multiform-mode)
  (setq read-extended-command-predicate       'command-completion-default-include-p
        vertico-count                         32  ; Show more candidates
        read-file-name-completion-ignore-case t   ; Ignore case of file names
        read-buffer-completion-ignore-case    t   ; Ignore case in buffer completion
        completion-ignore-case                t)) ; Ignore case in completion
#+end_src

** Vertico Posframe

Note: The latest updates to the underlying posframe lib break the visuals of the rounded, themed corners on my Mac, so I pin it directly here to avoid it upgrading to the latest version:

#+begin_src emacs-lisp
(use-package posframe
  :vc (:url "https://github.com/tumashu/posframe"
       :rev "v1.5.1"))
#+end_src

[[https://github.com/tumashu/vertico-posframe][vertico-posframe]] makes Vertico appear in a small child frame, instead of as a
traditional minibuffer. I like to have mine in the middle of the frame, with small fringes on either side.

I temporarily disable =vertico-posframe-mode= when searching with =consult=.
When selecting a search match, a preview is provided. That's kind of hard to see
with the posframe in the middle of the screen, so while searching I just use the
normal minibuffer.

#+begin_src emacs-lisp
(use-package vertico-posframe
  :init
  (setq vertico-posframe-parameters   '((left-fringe  . 12)    ;; Fringes
                                        (right-fringe . 12)
                                        (undecorated  . nil))) ;; Rounded frame
  :config
  (vertico-posframe-mode 1)
  (setq vertico-posframe-width        96                       ;; Narrow frame
        vertico-posframe-height       vertico-count            ;; Default height
        ;; Don't create posframe for these commands
        vertico-multiform-commands    '((consult-line    (:not posframe))
                                        (consult-ripgrep (:not posframe)))))
#+end_src

The rounded frame corners (putting =(undecorated . nil)= in the =vertico-posframe-parameters=) look really nice on Mac OS.

[[./images/vertico-posframe-screenshot.png]]

On Linux, however, this trick doesn't work unless your OS can create rounded windows.

** Corfu

[[https://github.com/minad/corfu][Corfu]] is a completion framework that provides text completion (like in-buffer auto-completion) via pop-up childframes.

It integrates with =Orderless=, the package below this, to provide fuzzy completions.

#+begin_src emacs-lisp
(use-package corfu
  :defer t
  :custom
  (corfu-auto          t)
  (corfu-auto-delay    0.1)
  (corfu-auto-prefix   1)
  (corfu-cycle         t)
  (corfu-quit-no-match 'separator)
  :bind (:map corfu-map
              (" " . corfu-insert-separator)) ;; Option + Space on Norwegian Mac
  :init
  (global-corfu-mode))

(setq tab-always-indent 'complete)
#+end_src

** Orderless

[[https://github.com/oantolin/orderless][Orderless]] is a package for a completion /style/, that matches multiple
regexes, in any order. It gives you fuzzy completions, which are super nice for typing things out faster.

#+begin_src emacs-lisp
(use-package orderless
  :ensure t
  :config
  (setq completion-styles '(orderless basic partial-completion)
        completion-category-overrides '((file (styles basic partial-completion)))))
#+end_src

* Search
** Search Utilities

Projectile also comes with a ton of built-in functionality to search in your projects. Other packages I use also depend on search utilities.

I use both [[https://github.com/BurntSushi/ripgrep][ripgrep]] and [[https://github.com/ggreer/the_silver_searcher][ag]] (The Silver Searcher). [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] also comes in handy sometimes. I'll install all the corresponding Emacs packages.

#+begin_src emacs-lisp
(use-package ripgrep
  :defer t)

(use-package rg
  :defer t)

(use-package ag
  :defer t)

(use-package wgrep
  :defer t)
#+end_src

I want to use [[https://github.com/BurntSushi/ripgrep][ripgrep]] as =grep=.

#+BEGIN_SRC emacs-lisp
(setq grep-command "rg -nS --no-heading "
      grep-use-null-device nil)
#+END_SRC

** Consult

[[https://github.com/minad/consult][Consult]] provides a /ton/ of search, navigation, and completion functionality. I
would definitely recommend looking at the documentation to learn more about all
that it can do.

I often press =C-x C-b= when I only mean to press =C-x b=. If I want to open the list of all buffers, I'll call it with =M-x list-buffers=, so let's rebind this one to the same as =C-x b= so save me some grief.

#+begin_src emacs-lisp
(use-package consult
  :bind (:map custom-bindings-map
              ("C-s"     . consult-line)
              ("C-M-s"   . consult-ripgrep)
              ("C-x b"   . consult-buffer)
              ("C-x C-b" . consult-buffer)
              ("M-g g"   . consult-goto-line)
              ("M-g t"   . consult-imenu)
              ("M-g a"   . consult-imenu-multi)))
#+end_src

** Imenu List

[[https://www.emacswiki.org/emacs/ImenuMode][Imenu]] is a built-in Emacs utility that gives you a minibuffer of the symbols in the current buffer and let's you jump to it. [[https://github.com/bmag/imenu-list][imenu-list]] is a nice package that gives you a new buffer with a navigable list of the functions, vars, etc. in your buffer, allowing you to quickly get an overview or jump to definition.

#+begin_src emacs-lisp
(use-package imenu-list
  :defer t
  :bind (:map custom-bindings-map
              ("M-g i" . imenu-list-smart-toggle)))
#+end_src

** Marginalia

[[https://github.com/minad/marginalia][Marginalia]] gives me annotations in the minibuffer.

#+begin_src emacs-lisp
(use-package marginalia
  :init 
  (marginalia-mode 1))
#+end_src

* Misc. Packages
** Version Control (Magit & Friends)

[[https://github.com/magit/magit][Magit]] is a Git client specifically for Emacs, and it's super powerful. It's the centre of all my version control packages.

*** Git Gutter with =diff-hl=

Let's first make sure we're highlighting uncommitted changes with [[https://github.com/dgutov/diff-hl][diff-hl]]. It highlights added, deleted, and modified code segments by adding a coloured bar to the left-hand gutter of the buffer.

#+begin_src emacs-lisp
(use-package diff-hl
  :config
  (global-diff-hl-mode))
#+end_src

*** Magit

Then configure Magit. I'll add hooks to have =diff-hl= update the gutter whenever Magit refreshes.

#+begin_src emacs-lisp
(use-package magit
  :defer t
  :bind (:map magit-mode-map
              ("C-M-f" . magit-section-forward)
              ("C-M-b" . magit-section-backward))
  :bind (:map custom-bindings-map
              ("M-g b" . magit-blame-addition))
  :hook
  ((magit-pre-refresh  . diff-hl-magit-pre-refresh)
   (magit-post-refresh . diff-hl-magit-post-refresh))
  :config
  (setq magit-mode-quit-window 'magit-restore-window-configuration
		magit-auto-revert-mode t)
  ; Remove tags from status buffer headings to speed up refresh slightly
  (remove-hook 'magit-status-sections-hook 'magit-insert-tags-header)
  ; Remove the "Head", "Merge", and "Push" status headers for even more speed!
  (remove-hook 'magit-status-sections-hook 'magit-insert-status-headers))
#+end_src

*** Magit Forge

And [[https://github.com/magit/forge][Magit Forge]] to be able to work with Git forges (e.g., GitHub, and GitLab) directly from Magit.

#+begin_src emacs-lisp
(use-package forge
  :after magit)
#+end_src

*** Git Link

[[https://github.com/sshaw/git-link/][git-link]] creates URL links to the current position in your buffer in the corresponding forge repo. Super handy for sending to others.

#+begin_src emacs-lisp
(use-package git-link
  :defer t
  :init
  (setq git-link-use-commit t
        git-link-open-in-browser t))
#+end_src

*** Git Timemachine

[[https://codeberg.org/pidu/git-timemachine][Git Time Machine]] lets you step through different versions of a Git-controlled file directly in the current buffer, without even needing to hop over to the Magit status buffer.

#+begin_src emacs-lisp
(use-package git-timemachine
  :defer t)
#+end_src

** Trying Packages

Lars Tveito's [[https://github.com/larstvei/Try][Try]] package lets you try out packages and only save them
temporarily, saving you the hassle of cleaning up afterwards if you decide you
don't want to keep using the package. You can even =try= packages from =.el= files
from URLs directly.

#+begin_src emacs-lisp
(use-package try)
#+end_src

** Snippets

[[https://github.com/joaotavora/yasnippet][YASnippet]] is a template system for Emacs that allows you to predefine snippets
you use often and insert them easily. I want snippets for basic Org-files,
Roam-notes, and other sequences often used.

#+begin_src emacs-lisp
(use-package yasnippet
  :diminish yas-minor-mode
  :defer 5
  :config
  (setq yas-snippet-dirs '("~/.emacs.d/snippets/"))
  (yas-global-mode 1)) ;; or M-x yas-reload-all if you've started YASnippet already.

;; Silences the warning when running a snippet with backticks (runs a command in the snippet)
(require 'warnings)
(add-to-list 'warning-suppress-types '(yasnippet backquote-change)) 
#+end_src

** Better Help Buffers

[[https://github.com/Wilfred/helpful][Helpful]] is an improvement on Emacs' built-in *help* buffer. It's more user-friendly and easier to read.

#+BEGIN_SRC emacs-lisp
(use-package helpful
  :bind (:map custom-bindings-map
			  ("C-h f" . helpful-callable) ; helpful-function++
			  ("C-h v" . helpful-variable)
			  ("C-h k" . helpful-key)
			  ("C-h x" . helpful-command)
			  ("C-h h" . helpful-at-point)))
#+END_SRC

[[https://github.com/justbur/emacs-which-key][which-key]] shows you available keybindings in the minibuffer. When you've started
to enter a command, it will show you where you can go from there.

#+begin_src emacs-lisp
(use-package which-key
  :config
  (which-key-mode))
#+end_src

** Jinx Spellchecker

[[https://github.com/minad/jinx][Jinx]] is a =libenchant=-powered spellchecker with a super nice UI. I'm
trying it out instead of Flyspell, which I used before.

#+begin_src emacs-lisp
(use-package jinx
  :hook (emacs-startup . global-jinx-mode)
  :bind (("M-$"   . jinx-correct)
         ("C-M-$" . jinx-languages))
  :config
  (setq jinx-languages "en_GB"))
#+end_src

** LaTeX

I use [[https://www.gnu.org/software/auctex/][AUCTeX]] to work with LaTeX files from within Emacs and it's a massive help.
It has a lot of different features, and I'd recommend checking out the
documentation to see all the stuff you can do with it.

I also really like =reftex-mode=, which gives you a table of contents with
clickable links for your file with the keybinding =C-c ==.

#+begin_src emacs-lisp
(use-package auctex
  :hook
  (LaTeX-mode . turn-on-prettify-symbols-mode)
  (LaTeX-mode . reftex-mode)
  (LaTeX-mode . outline-minor-mode)
  (LaTeX-mode . olivetti-mode))
#+end_src

When the =reftex= window opens, I want it on the left side of the screen and I
want it to take up less than half the screen.

#+begin_src emacs-lisp
(setq reftex-toc-split-windows-horizontally t
	  reftex-toc-split-windows-fraction     0.2)
#+end_src

** PDF Tools

[[https://github.com/vedang/pdf-tools][PDF Tools]] is an improved version of the built-in DocView for viewing PDFs. It has extensive features, but does not play well with =consult=, so I'll rebind =C-s= to =isearch-forward=.

#+begin_src emacs-lisp
(use-package pdf-tools
  :defer t
  :init (pdf-loader-install)
  :hook ((pdf-view-mode . (lambda () (auto-revert-mode -1)))
         (pdf-view-mode . (lambda () (company-mode -1))))
  :bind (:map pdf-view-mode-map
              ("C-s"   . isearch-forward)
              ("C-M-s" . pdf-occur)))
#+end_src

Warn me when a PDF has been opened with the default DocView mode instead of PDF Tools' PDF View mode.

#+begin_src emacs-lisp
(use-package doc-view
  :hook (doc-view-mode . (lambda ()
                           (display-warning
                            emacs
                            "Oops, using DocView instead of PDF Tools!"
                            :warning))))
#+end_src

[[https://github.com/nicolaisingh/saveplace-pdf-view][saveplace-pdf-view]] is a great package that remembers where in your PDFs you last left off, down to the scroll position and zoom amount.

#+begin_src emacs-lisp
(use-package pdf-view-restore
  :after pdf-tools
  :config
  (add-hook 'pdf-view-mode-hook 'pdf-view-restore-mode))
#+end_src

** EPUBs

[[https://depp.brause.cc/nov.el/][nov.el]] is a package for reading EPUBs (an e-book format) directly in Emacs.

#+begin_src emacs-lisp
(use-package nov
  :defer t
  :config
  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode)))
#+end_src

** Editor Config

I want to use the [[https://github.com/editorconfig/editorconfig-emacs][EditorConfig]] plugin, which helps maintain consistent coding
styles across editors when collaborating.

#+begin_src emacs-lisp
(use-package editorconfig
  :defer t)
#+end_src

** Browser Preference

Open links with Firefox by default.

#+begin_src emacs-lisp
(when (eq system-type 'darwin)
  (setq browse-url-browser-function 'browse-url-default-macosx-browser))

(when (eq system-type 'gnu/linux)
  (setq browse-url-browser-function 'browse-url-generic
		browse-url-generic-program "firefox"))
#+end_src

** Elfeed

[[https://github.com/skeeto/elfeed][Elfeed]] is a feed reader for Emacs!

#+begin_src emacs-lisp
(use-package elfeed
  :bind (:map custom-bindings-map ("C-x w" . elfeed))
  :config
  (setq elfeed-feeds
      '("http://nullprogram.com/feed/"
        "https://planet.emacslife.com/atom.xml"
        "https://deniskyashif.com/index.xml"
        "https://sophiebos.io/index.xml")))
#+end_src

** Config Profiling

[[https://github.com/jschaf/esup][ESUP]] is a package for profiling your config. You can use it to shave precious seconds off your
startup time, which is useful to me because I keep closing it when I'm done with
a task and then immediately needing it again.

#+begin_src emacs-lisp
(use-package esup
  :defer t
  :config
  (setq esup-depth 0))
#+end_src

* Org

[[https://orgmode.org/][Org Mode]] is a smart text system that is used for organising notes, literate programming, time management, and a wide variety of other use cases. I've been interested in switching from my previous note-taking app, Obsidian, to using Org and Roam (described in the next section).

** Visuals
*** Built-In Options & Hooks

Let's use some of the built-in options in org mode to declutter and get a consistent visual look and feel.

We'll also use [[https://orgmode.org/manual/Special-Symbols.html]["pretty entities"]], which allow us to insert special characters LaTeX-style by using a leading backslash (e.g., =\alpha= to write the greek letter alpha) and display ellipses in a condensed way.

#+BEGIN_SRC emacs-lisp
(use-package org
  :defer t
  :hook (org-mode . olivetti-mode)    ; Center text
  :config
  (org-indent-mode -1)                ; Don't indent text according to heading level
  (setq org-startup-folded   'content ; Fold headings by default
        org-hide-leading-stars      t ; Hide leading stars
        org-pretty-entities         t ; Use LaTeX-style pretty characters
        org-ellipsis            "  ·" ; Character shown after folded headings
        org-src-fontify-natively    t ; Fontify source blocks
        org-src-tab-acts-natively   t ; Have TAB act normally in source blocks
        org-src-content-indentation 0 ; 0 spaces of extra indentation
        org-special-ctrl-a/e        t ; Jump where I mean in Org headings++
        ;; Deal with TODOs and tags more nicely
        org-log-done                t
        org-auto-align-tags         t
        org-tags-column           -80
        ;; Try to act nicely when inserting content
        org-fold-catch-invisible-edits     'show-and-error
        org-insert-heading-respect-content t))
#+END_SRC

*** LaTeX Previews

#+begin_src emacs-lisp
(with-eval-after-load 'org
  ;; Create LaTeX previews on startup
  (setq org-startup-with-latex-preview t)
  ;; Increase size of LaTeX previews in Org mode
  (plist-put org-format-latex-options :scale 1.35))
#+end_src

I had been struggling to get LaTeX previews to work on my work Mac. I symlinked my LaTeX =texbin= directory to =/usr/local/bin=, and it still didn't work. Eventually I found [[https://emacs.stackexchange.com/a/80170][this]] Stack Exchange post that correctly diagnosed the issue.

#+begin_summary Explanation
The error said =[...] Please Adjust 'dvipng' part of 'org-preview-latex-process-alist'.=

The issue is caused by wrong expansion of the relative file path =%f= in the variable =org-preview-latex-process-alist=. Changing it to =%F= resolves the issue.
#+end_summary

#+begin_src emacs-lisp
(with-eval-after-load 'org
  (let ((png (cdr (assoc 'dvipng org-preview-latex-process-alist))))
    (plist-put png :latex-compiler '("latex -interaction nonstopmode -output-directory %o %F"))
    (plist-put png :image-converter '("dvipng -D %D -T tight -o %O %F"))
    (plist-put png :transparent-image-converter '("dvipng -D %D -T tight -bg Transparent -o %O %F"))))
#+end_src

*** Fonts

Set the sizes and fonts for the various headings.

#+begin_src emacs-lisp
;; Resize Org headings
(custom-set-faces
'(org-document-title ((t (:height 1.6))))
'(outline-1          ((t (:height 1.25))))
'(outline-2          ((t (:height 1.2))))
'(outline-3          ((t (:height 1.2))))
'(outline-4          ((t (:height 1.2))))
'(outline-5          ((t (:height 1.2))))
'(outline-6          ((t (:height 1.2))))
'(outline-7          ((t (:height 1.2))))
'(outline-8          ((t (:height 1.2))))
'(outline-9          ((t (:height 1.2)))))
#+end_src

*** Hide Emphasis Markers

Many people hide emphasis markers (e.g., =/.../= for italics, =*...*= for bold,
etc.) to have a cleaner visual look, but I got frustrated trying to go back and
edit text in these markers, as sometimes I would delete the markers itself or
write outside the markers. [[https://github.com/awth13/org-appear][org-appear]] is the solution to all my troubles. It
displays the markers when the cursor is within them and hides them otherwise,
making edits easy while looking pretty.

#+begin_src emacs-lisp
(use-package org-appear
  :commands (org-appear-mode)
  :hook     (org-mode . org-appear-mode)
  :config 
  (setq org-hide-emphasis-markers t)  ;; Must be activated for org-appear to work
  (setq org-appear-autoemphasis   t   ;; Show bold, italics, verbatim, etc.
        org-appear-autolinks      t   ;; Show links
        org-appear-autosubmarkers t)) ;; Show sub- and superscripts
#+end_src

*** Inline Images

Show inline images by default

#+begin_src haskell
(setq org-startup-with-inline-images t)
#+end_src

*** Variable Pitch

Make sure =variable-pitch-mode= is always active in Org buffers. I normally
wouldn't need this, since I use the =mixed-pitch= package in the font section, but
for some reason, it seems the header bullet in Org mode are affected by this.

#+begin_src emacs-lisp
(add-hook 'org-mode-hook 'variable-pitch-mode)
#+end_src

*** LaTeX Fragtog

[[https://github.com/io12/org-fragtog][org-fragtog]] works like org-appear, but for LaTeX fragments: It toggles LaTeX
previews on and off automatically, depending on the cursor position. If you move the
cursor to a preview, it's toggled off so you can edit the LaTeX snippet. When
you move the cursor away, the preview is turned on again.

#+begin_src emacs-lisp
(use-package org-fragtog
  :after org
  :hook (org-mode . org-fragtog-mode))
#+end_src

*** Bullets

=org-superstar= styles some of my UI elements, such as bullets and special
checkboxes for TODOs.

#+begin_src emacs-lisp
(use-package org-superstar
  :after org
  :config
  (setq org-superstar-leading-bullet "")
  (setq org-superstar-headline-bullets-list '("")) ;; Hide bullets
  (setq org-superstar-special-todo-items t)        ;; Makes TODO header bullets into boxes
  (setq org-superstar-todo-bullet-alist '(("TODO"     . 9744)
                                          ("PROG"     . 9744)
                                          ("NEXT"     . 9744)
                                          ("WAIT"     . 9744)
                                          ("DROP"     . 9744)
                                          ("QUESTION" . 9744)
                                          ("DONE"     . 9745)))
  :hook (org-mode . org-superstar-mode))
#+end_src

*** SVG Elements

[[https://github.com/rougier/svg-tag-mode][svg-tag-mode]] lets you replace keywords such as TODOs, tags, and progress bars with nice
SVG graphics. I use it for dates, progress bars, and citations.

#+begin_src emacs-lisp
(use-package svg-tag-mode
  :after org
  :config
  (defconst date-re "[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}")
  (defconst time-re "[0-9]\\{2\\}:[0-9]\\{2\\}")
  (defconst day-re "[A-Za-z]\\{3\\}")
  (defconst day-time-re (format "\\(%s\\)? ?\\(%s\\)?" day-re time-re))

  (defun svg-progress-percent (value)
	(svg-image (svg-lib-concat
				(svg-lib-progress-bar (/ (string-to-number value) 100.0)
			      nil :margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
				(svg-lib-tag (concat value "%")
				  nil :stroke 0 :margin 0)) :ascent 'center))

  (defun svg-progress-count (value)
	(let* ((seq (mapcar #'string-to-number (split-string value "/")))
           (count (float (car seq)))
           (total (float (cadr seq))))
	  (svg-image (svg-lib-concat
				  (svg-lib-progress-bar (/ count total) nil
					:margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
				  (svg-lib-tag value nil
					:stroke 0 :margin 0)) :ascent 'center)))
  (setq svg-tag-tags
      `(;; Org tags
        ;; (":\\([A-Za-z0-9]+\\)" . ((lambda (tag) (svg-tag-make tag))))
        ;; (":\\([A-Za-z0-9]+[ \-]\\)" . ((lambda (tag) tag)))
        
        ;; Task priority
        ("\\[#[A-Z]\\]" . ( (lambda (tag)
                              (svg-tag-make tag :face 'org-priority 
                                            :beg 2 :end -1 :margin 0))))

        ;; Progress
        ("\\(\\[[0-9]\\{1,3\\}%\\]\\)" . ((lambda (tag)
          (svg-progress-percent (substring tag 1 -2)))))
        ("\\(\\[[0-9]+/[0-9]+\\]\\)" . ((lambda (tag)
          (svg-progress-count (substring tag 1 -1)))))
        
        ;; TODO / DONE
        ;; ("TODO" . ((lambda (tag) (svg-tag-make "TODO" :face 'org-todo
		;; 									           :inverse t :margin 0))))
        ;; ("DONE" . ((lambda (tag) (svg-tag-make "DONE" :face 'org-done :margin 0))))


        ;; Citation of the form [cite:@Knuth:1984] 
        ("\\(\\[cite:@[A-Za-z]+:\\)" . ((lambda (tag)
                                          (svg-tag-make tag
                                                        :inverse t
                                                        :beg 7 :end -1
                                                        :crop-right t))))
        ("\\[cite:@[A-Za-z]+:\\([0-9]+\\]\\)" . ((lambda (tag)
                                                (svg-tag-make tag
                                                              :end -1
                                                              :crop-left t))))

        
        ;; Active date (with or without day name, with or without time)
        (,(format "\\(<%s>\\)" date-re) .
         ((lambda (tag)
            (svg-tag-make tag :beg 1 :end -1 :margin 0))))
        (,(format "\\(<%s \\)%s>" date-re day-time-re) .
         ((lambda (tag)
            (svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0))))
        (,(format "<%s \\(%s>\\)" date-re day-time-re) .
         ((lambda (tag)
            (svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0))))

        ;; Inactive date  (with or without day name, with or without time)
         (,(format "\\(\\[%s\\]\\)" date-re) .
          ((lambda (tag)
             (svg-tag-make tag :beg 1 :end -1 :margin 0 :face 'org-date))))
         (,(format "\\(\\[%s \\)%s\\]" date-re day-time-re) .
          ((lambda (tag)
             (svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0 :face 'org-date))))
         (,(format "\\[%s \\(%s\\]\\)" date-re day-time-re) .
          ((lambda (tag)
             (svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0 :face 'org-date)))))))

(add-hook 'org-mode-hook 'svg-tag-mode)
#+end_src

*** Prettify Tags & Keywords

I have a custom function to prettify tags and other elements, lifted from [[https://github.com/jakebox/jake-emacs/blob/main/jake-emacs/jib-funcs.el][Jake
B's Emacs setup]].

#+begin_src emacs-lisp
(defun soph/prettify-symbols-setup ()
  "Beautify keywords"
  (interactive)
  (setq prettify-symbols-alist
		(mapcan (lambda (x)
                  (when (and (consp x) (stringp (car x)))
                    (list x (cons (upcase (car x)) (cdr x)))))
				'(; Greek symbols
				  ("lambda" . ?λ)
				  ("delta"  . ?Δ)
				  ("gamma"  . ?Γ)
				  ("phi"    . ?φ)
				  ("psi"    . ?ψ)
                  ; Org headers
				  ("#+title:"  . ? )
				  ("#+author:" . ? )
                  ("#+date:"   . ? )                
                  ; Checkboxes
				  ("[ ]" . ?)
				  ("[X]" . ?)
				  ("[-]" . ?)
                  ; Blocks
				  ("#+begin_src"   . ?❯)
				  ("#+end_src"     . ?❯)
                  ("#+results:"   . ?»)
				  ("#+begin_QUOTE" . ?‟)
				  ("#+end_QUOTE"   . ?”)
                  ; Drawers
				  (":properties:" . ?)
                  ; Agenda scheduling
				  ("SCHEDULED:"   . ?🕘)
				  ("DEADLINE:"    . ?⏰)
                  ; Agenda tags  
				  (":@projects:"  . ?☕)
				  (":work:"       . ?🚀)
				  (":@inbox:"     . ?✉)
				  (":goal:"       . ?🎯)
				  (":task:"       . ?📋)
				  (":@thesis:"    . ?📝)
				  (":thesis:"     . ?📝)
				  (":emacs:"      . ?)
				  (":learn:"      . ?🌱)
				  (":code:"       . ?💻)
				  (":fix:"        . ?🛠)
				  (":bug:"        . ?🚩)
				  (":read:"       . ?📚)
                  ; Roam tags
				  ("#+filetags:"  . ?📎)
				  (":wip:"        . ?🏗)
				  (":ct:"         . ?➡)    ; Category Theory
                  (":verb:"       . ?🌐) ; HTTP Requests in Org mode
                  )))
  (prettify-symbols-mode))

(add-hook 'org-mode-hook        #'soph/prettify-symbols-setup)
(add-hook 'org-agenda-mode-hook #'soph/prettify-symbols-setup)
#+end_src

*** Right-Align Tags

Code snippet from [[https://www.reddit.com/r/emacs/comments/185e4k1/comment/kb39xvy/?utm_source=share&utm_medium=web2x&context=3][this Reddit post]]. It actually right-aligns tags, using
font-lock and the display property.

#+begin_src emacs-lisp
(add-to-list 'font-lock-extra-managed-props 'display)
(font-lock-add-keywords 'org-mode
                        `(("^.*?\\( \\)\\(:[[:alnum:]_@#%:]+:\\)$"
                           (1 `(face nil
                                     display (space :align-to (- right ,(org-string-width (match-string 2)) 3)))
                              prepend))) t)
#+end_src

** General Interaction

*** Disable Electric Indent Mode

The built-in electric indent mode is great - just not for Org mode.

#+begin_src emacs-lisp
(add-hook 'org-mode-hook #'(lambda () (electric-indent-local-mode -1)))
#+end_src

*** Opening Links

By default, when opening an Org-link, the current window is split into two. I'd like for the new window to replace the current one. To do this, we need to edit =org-link-frame-setup= and change the default cons =(file . find-file-other-window)= to =(file . find-file)=.

#+begin_src emacs-lisp :tangle no
(setq org-link-frame-setup
      '((vm      . vm-visit-folder-other-frame)
        (vm-imap . vm-visit-imap-folder-other-frame)
        (gnus    . org-gnus-no-new-news)
        (file    . find-file)
        (wl      . wl-other-frame)))
#+end_src

I'd also like to open links with =RET=.

#+begin_src emacs-lisp
(setq org-return-follows-link t)
#+end_src

*** Editing

Don't insert a blank newline before new entries (e.g., list bullets and section headings). I find it annoying when I want to insert a new task under the current one in my agenda if there's a blank newline between the previous entry and the next.

#+begin_src emacs-lisp
(setq org-blank-before-new-entry '((heading . nil)
                                   (plain-list-item . nil)))
#+end_src

** Agenda

First, some regular agenda settings.

I want to open my agenda on the current day, not on any specific weekday.

I also don't want to have a divider line separating my different agenda blocks. This is because I sometimes use packages like Olivetti to center the agenda, which makes the divider line wrap around and take up multiple lines.

Similarly, I right-align my tags, so they also end up shifted around and often on a new line. =org-agenda-remove-tags= doesn't remove them, but for some reason it disables the right-alignment in the agenda, which is perfect.

#+begin_src emacs-lisp
(setq org-agenda-start-on-weekday nil
      org-agenda-block-separator  nil
      org-agenda-remove-tags      t)
#+end_src

*** Super Agenda

[[https://github.com/alphapapa/org-super-agenda][org-super-agenda]] lets you group agenda items into sections, so it's easier to
navigate.

#+begin_src emacs-lisp
(use-package org-super-agenda
  :after org
  :config
  (setq org-super-agenda-header-prefix "\n❯ ")
  ;; Hide the thin width char glyph
  (add-hook 'org-agenda-mode-hook
            #'(lambda () (setq-local nobreak-char-display nil)))
  (org-super-agenda-mode))
#+end_src

*** Org QL

[[https://github.com/alphapapa/org-ql][org-ql]] is a query language for Org mode. It's super powerful and doesn't really belong in the Agenda section of my config, but for now, I only use it to find things and to set up a pretty calendar view.

One of the things I want to find regularly, is a list of all my TODOs marked with the custom state =QUESTION=. Usually, this is stuff that I want to bring up in my next meeting with someone, so it's handy to be able to pull up all the questions I have. =org-ql= is perfect for that.

#+begin_src emacs-lisp :tangle no
(use-package org-ql
  :after org
  :config
  (add-to-list 'org-ql-views
             '("Questions" :buffers-files org-agenda-files :query
               (and
                (not
                 (done))
                (todo "QUESTION"))
               :sort
               (todo priority date)
               :super-groups org-super-agenda-groups :title "Agenda-like")))
#+end_src
*** Agenda Views

With Super Agenda and Org QL, we can now define some display groups for the agenda, to show us exactly the info we want.

We'll set up some groups with the Super Agenda syntax.

#+begin_src emacs-lisp
;; Delete default agenda commands
(setq org-agenda-custom-commands nil)

(defvar regular-view-groups
  '((:name "Scheduled"
     :scheduled t
     :order 1)
	(:name "Deadlines"
     :deadline t
     :order 2)))
#+end_src

Now I'll set up commands to open the day view with =C-c a d= and extended three-day view with =C-c a e=. Notice that I'm first setting some options for the built-in agenda, and then defining a block with Super Agenda groups and Org QL queries.

#+begin_src emacs-lisp
(add-to-list 'org-agenda-custom-commands
	  '("d" "Day View"
		 ((agenda "" ((org-agenda-overriding-header "Day View")
                      (org-agenda-span 'day)
                      (org-super-agenda-groups regular-view-groups)))
		  (org-ql-block '(todo "PROG") ((org-ql-block-header "\n❯ In Progress")))
		  (org-ql-block '(todo "NEXT") ((org-ql-block-header "\n❯ Next Up")))
          (org-ql-block '(todo "WAIT") ((org-ql-block-header "\n❯ Backlog")))
		  (org-ql-block '(priority "A") ((org-ql-block-header "\n❯ Important"))))))


(add-to-list 'org-agenda-custom-commands
		'("e" "Three-Day View"
               ((agenda "" ((org-agenda-span 3)
                            (org-agenda-start-on-weekday nil)
                            (org-deadline-warning-days 0))))))
#+end_src

*** Displaying Scheduled & Deadline Items

Don't show me deadlines or scheduled items if they are done.

#+begin_src emacs-lisp
(setq org-agenda-skip-deadline-if-done  t
	  org-agenda-skip-scheduled-if-done t)
#+end_src

Modify dealine leader text.

#+begin_src emacs-lisp
(setq org-agenda-deadline-leaders '("Deadline:  " "In %2d d.: " "%2d d. ago: "))
#+end_src

** Tasks
*** Task Priorities

Let's increase the number of possible priorities for Org tasks. I'll set
mine to =E= so that we have =A= through =E=, in total five levels.

#+begin_src emacs-lisp
(setq org-lowest-priority  ?F) ;; Gives us priorities A through F
(setq org-default-priority ?E) ;; If an item has no priority, it is considered [#E].

(setq org-priority-faces
      '((65 . "#BF616A")
        (66 . "#EBCB8B")
        (67 . "#B48EAD")
        (68 . "#81A1C1")
        (69 . "#5E81AC")
        (70 . "#4C566A")))
#+end_src

*** Custom TODO States

I'll expand the list of default task states.

#+begin_src emacs-lisp
(setq org-todo-keywords
      '((sequence
         ;; Needs further action
		 "TODO(t)" "PROG(p)" "NEXT(n)" "WAIT(w)" "QUESTION(q) DROP(x)"
		 "|"
         ;; Needs no action currently
		 "DONE(d)")))
#+end_src

*** Mark As Done

Finally, to mark any TODO task, of any state, as DONE quickly, I have a helper
function that I'll bind to =C-c d=.

#+begin_src emacs-lisp
(defun org-mark-as-done ()
  (interactive)
  (save-excursion
    (org-back-to-heading t) ;; Make sure command works even if point is
                            ;; below target heading
    (cond ((looking-at "\*+ TODO")
           (org-todo "DONE"))
		  ((looking-at "\*+ NEXT")
           (org-todo "DONE"))
          ((looking-at "\*+ WAIT")
           (org-todo "DONE"))
		  ((looking-at "\*+ PROG")
           (org-todo "DONE"))
		  ((looking-at "\*+ DROP")
           (org-todo "DONE"))
		  ((looking-at "\*+ DROP")
           (org-todo "QUESTION"))
		  ((looking-at "\*+ DONE")
           (org-todo "DONE"))
          (t (message "Undefined TODO state.")))))
#+end_src

*** Hide DONE Tasks

[[https://github.com/vapniks/hide-lines.git][hide-lines]] is a package that allows us to hide lines in a buffer based on a regexp. I use it to optionally hide elements from my org task files. Sometimes, I'm not ready to archive the task yet, but I want to be able to temporarily hide the tasks marked as "DONE".

#+begin_src emacs-lisp
(use-package hide-lines
  :vc (:url "https://github.com/vapniks/hide-lines.git"
       :branch "master"
       :rev :newest))

(defun hide-done-tasks ()
  (interactive)
  (hide-lines-matching "* DONE"))

(defun show-done-tasks ()
  (interactive)
  (hide-lines-show-all))
#+end_src

*** "Get Things Done" Setup

I'm trying out the Get Things Done method by David Allen, using Nicolas
Rougier's [[https://github.com/rougier/emacs-gtd][GTD configuration]] and Nicolas Petton's [[https://emacs.cafe/emacs/orgmode/gtd/2017/06/30/orgmode-gtd.html][blog post]] on the subject.

The first step is to set the relevant directories.

#+BEGIN_SRC emacs-lisp
(setq org-directory "~/Dropbox/org/")
(add-to-list 'org-agenda-files "inbox.org")
#+END_SRC

Set the archive location to a unified archive.

#+begin_src emacs-lisp
(setq org-archive-location (concat org-directory "archive.org::"))
#+end_src

Then to set up the relevant capture templates, with accompanying keybindings.

#+BEGIN_SRC emacs-lisp
(setq org-capture-templates
       `(("i" "Inbox" entry  (file "inbox.org")
        ,(concat "* TODO %?\n"
                 "/Entered on/ %U"))))
(defun org-capture-inbox ()
     (interactive)
     (call-interactively 'org-store-link)
     (org-capture nil "i"))
#+END_SRC

*** Keybindings

For basic agenda and TODO-related keybindings, I'll use =C-c= followed by a
single, lower-case letter.

#+BEGIN_SRC emacs-lisp
(define-key custom-bindings-map (kbd "C-c l") 'org-store-link)
(define-key custom-bindings-map (kbd "C-c a") 'org-agenda)
(define-key custom-bindings-map (kbd "C-c c") 'org-capture)

(with-eval-after-load 'org
  (define-key org-mode-map (kbd "C-c t") 'org-todo)
  (define-key org-mode-map (kbd "C-c d") 'org-mark-as-done))
#+END_SRC

For whatever reason, I've had an issue with clocking in, where the default
keybinding used =TAB= instead of =C-i= to clock in, so I'll set that manually.

#+begin_src emacs-lisp :tangle no
(define-key org-mode-map (kbd "C-c C-x C-i") 'org-clock-in)
#+end_src

*** Registers

[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Registers.html][Registers]] are easier to access than bookmarks and much more flexible. I'll set
up registers for my GTD files.

#+begin_src emacs-lisp
(set-register ?i (cons 'file (concat org-directory "inbox.org")))
(set-register ?r (cons 'file (concat org-directory "roam/20240128135100-roam.org")))
(set-register ?p (cons 'file (concat org-directory "projects.org")))
(set-register ?c (cons 'file "~/Dropbox/playground/clj-playground/src/clj_playground/playground.clj"))
(set-register ?b (cons 'file "~/Dropbox/projects/blog/org-content/all-posts.org"))
#+end_src

Since I have =C-s= bound to =consult-line= which lets me search everywhere in a
file, I don't really need =C-r= to be bound to the default =isearch-backward=.
Instead, I can use it as the leader key combination to jump to a register.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-r") 'jump-to-register)
#+end_src

** Babel

For working with code blocks in Org mode, I want to make sure code blocks are
not evaluated by default on export. I also want to add some languages.

#+begin_src emacs-lisp
(setq org-export-use-babel       nil
      org-confirm-babel-evaluate nil)
(org-babel-do-load-languages
 'org-babel-load-languages
 '((emacs-lisp . t)
   (python     . t)
   (haskell    . t)
   (clojure    . t)))
#+end_src

For Python, use whatever interpreter is set by =python-shell-interpreter=.

#+begin_src emacs-lisp
(use-package ob-python
  :ensure nil
  :after (ob python)
  :config
  (setq org-babel-python-command python-shell-interpreter))
#+end_src

** Roam   

[[https://roamresearch.com/][Roam]] is a smart note-taking system in the style of a personal knowledge
management system. [[https://www.orgroam.com/][org-roam]] is a port of this system that uses all plain-text Org-files.

I set up a Roam directory and added a simple configuration for navigating Roam nodes.

#+begin_src emacs-lisp
(use-package org-roam
  :after org
  :hook (org-roam-mode . org-roam-db-autosync-mode)
  :init
  (setq org-roam-v2-ack t)
  :custom
  (org-roam-directory "~/Dropbox/org/roam")
  (org-roam-completion-everywhere t)
  :bind
  ("C-c n t" . org-roam-buffer-toggle)
  ("C-c n f" . org-roam-node-find)
  ("C-c n i" . org-roam-node-insert)
  ("C-c q"   . org-roam-tag-add)
  :config
  ;; Sync my Org Roam database automatically
  (org-roam-db-autosync-mode)
  ;; Open Org files in same window
  (add-to-list 'org-link-frame-setup '(file . find-file)))
#+end_src

*** Consult Org Roam

#+begin_src emacs-lisp
(use-package consult-org-roam
   :ensure t
   :after org-roam
   :init
   (require 'consult-org-roam)
   ;; Activate the minor mode
   (consult-org-roam-mode 1)
   :custom
   ;; Use `ripgrep' for searching with `consult-org-roam-search'
   (consult-org-roam-grep-func #'consult-ripgrep)
   ;; Configure a custom narrow key for `consult-buffer'
   (consult-org-roam-buffer-narrow-key ?r)
   ;; Display org-roam buffers right after non-org-roam buffers
   ;; in consult-buffer (and not down at the bottom)
   (consult-org-roam-buffer-after-buffers t)
   :config
   ;; Eventually suppress previewing for certain functions
   (consult-customize
    consult-org-roam-forward-links
    :preview-key "M-.")
   :bind
   ;; Define some convenient Org Roam keybindings
   ("C-c n e" . consult-org-roam-file-find)
   ("C-c n b" . consult-org-roam-backlinks)
   ("C-c n l" . consult-org-roam-forward-links)
   ("C-c n r" . consult-org-roam-search))
#+end_src

*** Show Tags in Search

When searching for nodes, you can search either by name or by tag. Both are
shown in the menu.

#+begin_src emacs-lisp
(setq org-roam-node-display-template
      (concat "${title:*} "
        (propertize "${tags:10}" 'face 'org-tag)))
#+end_src

*** Graph UI

[[https://github.com/org-roam/org-roam-ui][Org Roam UI]] gives you a pretty and functional graph of your notes, Obsidian-style.

#+begin_src emacs-lisp
(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))
#+end_src

** Hugo

[[https://gohugo.io/][Hugo]] is a static site generator. By default, it uses a Markdown flavour called
Blackfriday. The package [[https://github.com/kaushalmodi/ox-hugo/][ox-hugo]] can export Org files to this format, and
also generate appropriate front-matter. I use it to write my blog in Org and
easily put it online.

#+BEGIN_SRC emacs-lisp
(use-package ox-hugo
  :after org)
#+END_SRC

I've had a great time blogging with =ox-hugo=, but it's a little bothersome to
have to rewrite the front-matter required in the blog post for it to export
property every time, so below is a little snippet lifted from [[https://ox-hugo.scripter.co/doc/org-capture-setup/][ox-hugo's blog]].

The file =all-posts,org= needs to be present in 'org-directory' and the file's
heading must be "Blog Posts". It can even be a symlink pointing to the actual location of all-posts.org! If you've named yours differently, change these values.

#+begin_src emacs-lisp
(with-eval-after-load 'org-capture
  (defun org-hugo-new-subtree-post-capture-template ()
    "Returns `org-capture' template string for new Hugo post.
See `org-capture-templates' for more information."
    (let* ((title (read-from-minibuffer "Post Title: "))
           (fname (org-hugo-slug title)))
      (mapconcat #'identity
                 `(
                   ,(concat "* TODO " title)
                   ":PROPERTIES:"
                   ,(concat ":EXPORT_FILE_NAME: " fname)
                   ":END:"
                   "%?\n")          ;Place the cursor here finally
                 "\n")))

  (add-to-list 'org-capture-templates
               '("h"                ;`org-capture' binding + h
                 "Hugo post"
                 entry
                 (file+olp "all-posts.org" "Blog Posts")
                 (function org-hugo-new-subtree-post-capture-template))))
#+end_src

** Org Present

[[https://github.com/rlister/org-present][org-present]] is a mode for creating straightforward and nice presentations from Org-files. Most of this config is from [[https://systemcrafters.net/emacs-tips/presentations-with-org-present/][System Crafters' blog post]] on the subject.

#+begin_src emacs-lisp
(defun soph/org-present-prepare-slide ()
  ;; Show only top-level headlines
  (org-overview)
  ;; Unfold the current entry
  (org-fold-show-entry)
  ;; Show only direct subheadings of the slide but don't expand them
  (org-fold-show-children))

(defun soph/org-present-start ()
  ;; Tweak font sizes
  (setq-local
   face-remapping-alist '((default (:height 1.5) variable-pitch)
                          (header-line (:height 3.0) variable-pitch)
                          (org-document-title (:height 1.75) org-document-title)
                          (org-block-begin-line (:height 0.7) org-block)))
  ;; Set a blank header line string to create blank space at the top
  (setq header-line-format " "))

(defun soph/org-present-end ()
  ;; Reset font customizations
  (setq-local face-remapping-alist '((default variable-pitch default)))
  ;; Clear the header line string so that it isn't displayed
  (setq header-line-format nil))

(use-package org-present
  :defer t
  :hook
  ((org-present-after-navigate-functions . soph/org-present-prepare-slide)
   (org-present-mode                     . soph/org-present-start)
   (org-present-mode-quit                . soph/org-present-end)))
#+end_src

** Org Conveniencies
*** Pasting Images with =org-download=

[[https://github.com/abo-abo/org-download][org-download]] lets me easily put copied screenshots into my org-documents.

#+begin_src emacs-lisp
(use-package org-download
  :after org
  :bind
  (:map org-mode-map
        (("s-t"   . org-download-screenshot)
         ("s-y"   . org-download-clipboard)
         ("C-M-y" . org-download-clipboard))))
#+end_src

*** Display inline images by default

This option displays inline images in Org mode by default when opening a file. The default setting is to not do so.

#+begin_src emacs-lisp
(setq org-startup-with-inline-images t)
#+end_src

*** TOC in Org Files

[[https://github.com/snosov1/toc-org][toc-org]] creates nice, Markdown compatible tables of content for your Org files.
Perfect for GitHub READMEs.

#+begin_src emacs-lisp
(use-package toc-org
  :after org
  :config
  (add-hook 'org-mode-hook 'toc-org-mode)

  ;; enable in markdown, too
  (add-hook 'markdown-mode-hook 'toc-org-mode))
#+end_src

* Programming
** Preferences & Extras
*** Custom File Endings

For my MSc thesis, I'm implementing a small functional programming language
called Contra. It's pretty similar to Haskell, so using Haskell mode does a
fairly good job of syntax highlighting my =.con=-files.

#+begin_src emacs-lisp
(add-to-list 'auto-mode-alist '("\\.con\\'" . haskell-mode))
#+end_src

*** Language-Specific Commenting

I use =C-ø= to comment/uncomment lines with [[https://github.com/redguardtoo/evil-nerd-commenter][Evil Nerd Commenter]]. It automatically
detects most programming languages and applies appropriate comment style.

#+begin_src emacs-lisp
(use-package evil-nerd-commenter
  :defer t
  :bind (:map custom-bindings-map ("C-ø" . evilnc-comment-or-uncomment-lines)))
#+end_src

*** Subword Mode

[[https://wikemacs.org/wiki/Subword-mode][subword-mode]] lets you work on each subword in camel case words as individual
words. It makes it much easier to delete and mark parts of function and variable
names.

#+begin_src emacs-lisp
(add-hook 'prog-mode-hook 'subword-mode)
#+end_src

*** Electric Pair Mode

[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Matching.html][electric-pair-mode]] is a built-in Emacs mode that will try to insert matching delimiters automatically. It's pretty handy.

#+begin_src emacs-lisp
(electric-pair-mode 1)
#+end_src

*** Highlight TODOs

It's nice to highlight TODOs in the code. [[https://github.com/tarsius/hl-todo][hl-todo]] does just that and lets you specify the faces to use for different TODO keywords. Let's activate it in =prog-mode=.

#+begin_src emacs-lisp
(use-package hl-todo
    :hook (prog-mode . hl-todo-mode)
    :config
    (setq hl-todo-highlight-punctuation ":"
          hl-todo-keyword-faces
          `(("TODO"       font-lock-keyword-face  bold)
            ("FIXME"      error                   bold)
            ("HACK"       font-lock-constant-face bold)
            ("REVIEW"     font-lock-keyword-face  bold)
            ("NOTE"       success                 bold)
            ("DEPRECATED" font-lock-doc-face      bold))))
#+end_src

*** Markdown

Need-to-have for programmers.

#+begin_src emacs-lisp
(use-package markdown-mode
  :defer t)
#+end_src

** Flycheck

[[https://github.com/flycheck/flycheck][Flycheck]] is an on-the-fly syntax checker.

I'll turn off the error messages in the echo area, because they overlap with useful info from LSP mode. I can still see the error, either by hovering over it with the mouse or by pressing =M--= or hovering over it.

After version 37.0, Flycheck's error list shows up at the bottom of the screen by default. I'm a strict two-windows max, side-by-side split kind of person, so I'm overriding that too.

#+begin_src emacs-lisp
(global-unset-key (kbd "M--"))

(use-package flycheck
  :defer t
  :init (global-flycheck-mode)
  :bind (:map custom-bindings-map
              ("M-- -"    . flycheck-explain-error-at-point)
              ("M-- M--"  . flycheck-explain-error-at-point)
              ("M-- ?"    . flycheck-describe-checker)
              ("M-- C"    . flycheck-clear)
              ("M-- C-c"  . flycheck-compile)
              ("M-- C-w"  . flycheck-copy-errors-as-kill)
              ("M-- H"    . display-local-help)
              ("M-- V"    . flycheck-version)
              ("M-- c"    . flycheck-buffer)
              ("M-- e"    . flycheck-explain-error-at-point)
              ("M-- h"    . flycheck-display-error-at-point)
              ("M-- i"    . flycheck-manual)
              ("M-- l"    . flycheck-list-errors)
              ("M-- n"    . flycheck-next-error)
              ("M-- p"    . flycheck-previous-error)
              ("M-- s"    . flycheck-select-checker)
              ("M-- v"    . flycheck-verify-setup)
              ("M-- x"    . flycheck-disable-checker))
  :config
  (setq flycheck-display-errors-function #'ignore
        flycheck-checker-error-threshold 500)
  (add-hook 'flycheck-after-syntax-check-hook #'soph/update-flycheck-modeline)
  (add-hook 'flycheck-mode-hook #'soph/update-flycheck-modeline)
  (add-hook 'after-save-hook
            (lambda ()
              (when (bound-and-true-p flycheck-mode)
                (flycheck-buffer))))
  (add-hook 'window-buffer-change-functions
            (lambda (_)
              (when (bound-and-true-p flycheck-mode)
                (flycheck-buffer))))
  ;; Display error list in existing side buffer
  (setq flycheck-error-list-display-buffer-action
        '((display-buffer-reuse-window) ; reuse window when possible
          (side . right)))              ; show on the right by default
  :custom
  (flycheck-disabled-checkers '(org-lint)))
#+end_src

** Eldoc

[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Programming-Language-Doc.html][Eldoc]] is Emacs' built-in language documentation feature. It will show function documentation as applicable while you're programming.

#+begin_src emacs-lisp
(use-package eldoc
  :defer t
  :config
  (global-eldoc-mode))
#+end_src

** xref

=xref= is Emacs' built-in cross-reference system. It allows you to follow references and builds a reference stack. For each reference you jump to, a new item is added to the stack, and you can go back to where you were before by popping it off the stack. It's super neat.

[[https://github.com/brett-lempereur/consult-xref-stack][consult-xref-stack]] is a package that enhances =xref= with a searchable list of the references you've navigated through. Even neater!

#+begin_src emacs-lisp
(use-package consult-xref-stack
  :vc
  (:url "https://github.com/brett-lempereur/consult-xref-stack" :branch "main")
  :bind (:map custom-bindings-map
              ("C-," . consult-xref-stack-backward)
              ("C-." . consult-xref-stack-forward)))
#+end_src

** HTTP Requests

[[https://github.com/pashky/restclient.el][restclient.el]] lets you run HTTP requests from a static, plain-text query file. As of April 17 2024, it is unfortunately archived.

#+begin_src emacs-lisp
(use-package restclient
  :defer t)
#+end_src

[[https://github.com/federicotdn/verb][verb]] is a package built on the same concept: Write queries in Org mode, send HTTP requests, and view the results pretty-printed in a new buffer.

#+begin_src emacs-lisp
(use-package verb
  :after org
  :config
  (define-key org-mode-map (kbd "C-c C-r") verb-command-map))
#+end_src

** Tree-Sitter

Tree-sitter is already [[https://lists.gnu.org/archive/html/emacs-devel/2022-11/msg01443.html][built into Emacs 29+]], so I don't need to install anything for that specifically, but I don't want to manage grammars and choosing modes manually.

The package [[https://github.com/renzmann/treesit-auto][treesit-auto]] will automatically try to install tree-sitter language grammars for you and use the tree-sitter version of a major mode (like =clojure-ts-mode=) instead of its normal major mode (like =clojure-mode=), and fall back when that's not possible.

#+begin_src emacs-lisp
(use-package treesit-auto
  :custom
  (treesit-auto-install 'prompt)
  (treesit-auto-langs
   '(bash c clojure css go html javascript make markdown python rust toml typescript tsx yaml))
  :config
  (treesit-auto-add-to-auto-mode-alist 'all)
  (global-treesit-auto-mode))
#+end_src

** Structural Editing

[[https://github.com/Fuco1/smartparens][Smartparens]] is a structural editing package that supports many languages out of the box. I've previously used [[https://www.emacswiki.org/emacs/ParEdit][Paredit]] and [[https://github.com/abo-abo/lispy][lispy]], but neither worked particularly well for TypeScript/JavaScript, which I use at work. Once you get used to structural editing, you just can't (or at least don't want to!) live without it, so I'm trying out Smartparens instead.

It's a lot more powerful than =eletric-pair-mode=, because it allows you to manipulate, select, and navigate forms semantically (by symbols or delimiters).

However, I did have just a few functions from lispy that I don't want to live without, among them:
- =lispy-different= - Switch between the opening and closing delimiters.
- =lispy-comment= - Insert the appropriate number of semicolons and automatically adjust the whitespace before them.
- =lispy-clone= - Clone the current sexp on and paste it on the line below (sp has this as well, but I like the behaviour of lispy's version better).
- =lispy-kill= - Kill the sexp after point. I like it better than the sp version.
- =lispy-move-up= - Move sexp up down, while keeping delimiters balanced.
- =lispy-move-down= - Move sexp down a line, while keeping delimiters balanced.

It's a bit insane to pull in lispy just for these functions, but I've really grown attached to these functions and it's my config, so welcome to the wild west!

#+begin_src emacs-lisp
(use-package lispy
  :defer t
  :config
  (setcdr lispy-mode-map nil)
  :commands
  lispy-different
  lispy-comment
  lispy-move-up
  lispy-mode-down
  lispy-clone
  lispy-kill)

(use-package smartparens
  :hook ((prog-mode        . smartparens-mode)
         (smartparens-mode . (lambda () (electric-pair-local-mode -1))))
  :config
  (require 'smartparens-config)
  (setq smartparens-strict-mode t)
  ;; Don't highlight delimiters when created
  (setq sp-highlight-pair-overlay     nil
        sp-highlight-wrap-overlay     nil
        sp-highlight-wrap-tag-overlay nil)
  ;; Be strict with deleting/creating parens
  (smartparens-global-strict-mode     t)
  :bind (:map smartparens-mode-map

              ;; Navigating
              ("C-f"         . sp-forward-sexp)
              ("C-b"         . sp-backward-sexp)
              ("M-A"         . sp-beginning-of-sexp)
              ("M-E"         . sp-end-of-sexp)
              ("C-M-f"       . sp-up-sexp)
              ("C-M-b"       . sp-backward-up-sexp)

              ;; Slurping & barfing
              ("C-<right>"   . sp-forward-slurp-sexp)
              ("C-<left>"    . sp-forward-barf-sexp)
              ("C-M-<right>" . sp-backward-barf-sexp)
              ("C-M-<left>"  . sp-backward-slurp-sexp)

              ;; Manipulating
              ("M-s"         . sp-splice-sexp)
              ("M-r"         . sp-raise-sexp)
              ("M-<up>"      . sp-splice-sexp-killing-backward)
              ("M-<down>"    . sp-splice-sexp-killing-forward)
              ("C-M-t"       . sp-transpose-sexp)
              ("M-?"         . sp-convolute-sexp)
              ("M-S"         . sp-split-sexp)
              ("M-J"         . sp-join-sexp)

              ;; --- Functions from lispy ---
              ;; Navigating
              ("M-d"         . lispy-different)
              ("C-M-<up>"    . lispy-move-up)
              ("C-M-<down>"  . lispy-move-down)

              ;; Manipulating
              ("M-c"         . lispy-clone)
              ("C-k"         . lispy-kill))
  :config
  ;; Unbind, to avoid conflict with my custom-bindings-map
  (define-key smartparens-strict-mode-map [remap kill-whole-line] nil))
#+end_src

** LSP

[[https://github.com/emacs-lsp/lsp-mode][lsp-mode]] is an Emacs client for the Language Server Protocol (LSP). I have LSP mode setup for Clojure and TypeScript.

I disable a few of the default features. If you want to know more about these and how to enable/disable other =lsp-mode= features, there's a handy guide on [[https://emacs-lsp.github.io/lsp-mode/tutorials/how-to-turn-off/][lsp-mode's website]].

#+begin_src emacs-lisp
(defun soph/maybe-start-lsp ()
    (when buffer-file-name
      ;; Don't start LSP mode when I'm looking at downloaded sources, like clojure.core
      (unless (or (string-prefix-p (expand-file-name "~/.m2/") buffer-file-name)
                  (string-prefix-p (expand-file-name "~/.gitlibs/") buffer-file-name))
        (lsp))))

(use-package lsp-mode
  :defer t
  :init
  (setq lsp-use-plists t)
  :hook ((clojure-ts-mode       . soph/maybe-start-lsp)
         (clojurec-mode         . soph/maybe-start-lsp)
         (lsp-mode              . lsp-enable-which-key-integration)
         (typescript-mode       . lsp)
         (tsx-ts-mode           . lsp)
         (typescript-ts-mode    . lsp)
         (web-mode              . lsp))
  :bind (:map lsp-mode-map
              ("M-<return>" . lsp-execute-code-action)
              ("C-M-."      . lsp-find-references)
              ("C-c r"      . lsp-rename))
  :config
  (setq lsp-diagnostics-provider :flycheck
        lsp-completion-provider  :none)       ;; I use corfu
  ;; Disable visual features
  (setq lsp-headerline-breadcrumb-enable nil  ;; No breadcrumbs
        lsp-lens-enable                  nil  ;; No lenses
        lsp-enable-symbol-highlighting   nil  ;; Don't highlight current symbol

        lsp-file-watch-threshold         2000
        lsp-format-buffer-on-save        nil

        ;; Limit raising of the echo area to show docs
        lsp-signature-doc-lines          3)

  (with-eval-after-load 'lsp-modeline
    (set-face-attribute 'lsp-modeline-code-actions-preferred-face nil
                        :inherit 'font-lock-comment-face)
    (set-face-attribute 'lsp-modeline-code-actions-face nil
                        :inherit 'font-lock-comment-face)))
#+end_src

For Clojure in particular, I want to use CIDER's completions first, and LSP completion only as a fallback. Luckily, the team over at Mattilsynet have already figrued out [[https://github.com/magnars/emacsd-reboot/blob/39c6cbf35cf1618d18d2c103b36db504869a9379/packages/setup-lsp-mode.el#L34-L40][a way to do this]]:

#+begin_src emacs-lisp
;; From https://github.com/magnars/emacsd-reboot/blob/39c6cbf35cf1618d18d2c103b36db504869a9379/packages/setup-lsp-mode.el#L34-L40
(defun soph/use-lsp-completion-only-as-fallback ()
  (when (-contains? completion-at-point-functions #'lsp-completion-at-point)
    (remove-hook 'completion-at-point-functions #'tags-completion-at-point-function t)
    (remove-hook 'completion-at-point-functions #'lsp-completion-at-point t)
    (remove-hook 'completion-at-point-functions t t)
    (add-to-list 'completion-at-point-functions #'lsp-completion-at-point t)
    (add-to-list 'completion-at-point-functions t t)))

(add-hook 'lsp-completion-mode-hook 'soph/use-lsp-completion-only-as-fallback)
#+end_src

[[https://github.com/emacs-lsp/lsp-ui][lsp-ui]] is an extension of the UI capabilities of =lsp-mode=.

#+begin_src emacs-lisp
(use-package lsp-ui
  :after lsp-mode
  :config
  (setq lsp-ui-sideline-enable nil
        lsp-ui-doc-enable      nil))
#+end_src

Similarly, there's [[https://github.com/emacs-lsp/lsp-treemacs][lsp-treemacs]] which I use to display a pretty list of references and a function's incoming call hierarchy. As a tree, of course!

#+begin_src emacs-lisp
(use-package lsp-treemacs
  :after lsp-mode
  :bind (:map lsp-mode-map
              ("C-c ." . lsp-treemacs-references)
              ("C-c :" . lsp-treemacs-call-hierarchy)))
#+end_src

There's also a nice mode for language-aware folding called [[https://github.com/gregsexton/origami.el][Origami]]. Then there's the LSP-backed [[https://github.com/emacs-lsp/lsp-origami/][lsp-origami]].

#+begin_src emacs-lisp
(use-package origami
  :defer t
  :bind (:map origami-mode-map
              ("C-z" . origami-toggle-node)))

(use-package lsp-origami
  :after lsp
  :hook (lsp-mode . lsp-origami-try-enable))
#+end_src

[[https://github.com/blahgeek/emacs-lsp-booster][emacs-lsp-booster]] is a wrapper around your LSP server programs. In the README, the authors explain that it helps speed up LSP mode (and Eglot!) by converting JSON directly into elisp bytecode and by separating reading and writing into different threads.

#+begin_src emacs-lisp
(defun lsp-booster--advice-json-parse (old-fn &rest args)
  "Try to parse bytecode instead of json."
  (or
   (when (equal (following-char) ?#)
     (let ((bytecode (read (current-buffer))))
       (when (byte-code-function-p bytecode)
         (funcall bytecode))))
   (apply old-fn args)))
(advice-add (if (progn (require 'json)
                       (fboundp 'json-parse-buffer))
                'json-parse-buffer
              'json-read)
            :around
            #'lsp-booster--advice-json-parse)

(defun lsp-booster--advice-final-command (old-fn cmd &optional test?)
  "Prepend emacs-lsp-booster command to lsp CMD."
  (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)))
(advice-add 'lsp-resolve-final-command :around #'lsp-booster--advice-final-command)
#+end_src

** Programming Languages
*** Clojure

I've switched to [[https://github.com/clojure-emacs/clojure-ts-mode][clojure-ts-mode]], which is near feature parity with the original [[https://github.com/clojure-emacs/clojure-mode][clojure-mode]] that I used before.

It has some great [[https://github.com/clojure-emacs/clojure-ts-mode?tab=readme-ov-file#refactoring-support][refactoring features]] under a specific mode map called =clojure-ts-refactor-map=, but they're a little finicky to access with the prefix combination =C-c C-r= folowed by a final command on the form =C-...=.

But luckily, there's a variable called =clojure-ts-refactor-map-prefix= to set this prefix. I've also set a few top-level ones directly in =clojure-ts-mode-map=.

#+begin_src emacs-lisp
(use-package clojure-ts-mode
  :defer t
  :hook ((clojure-ts-mode . whitespace-mode)
         ;; I think the font locking (syntax highlighting) of numbers in clojure-ts-mode
         ;; is ugly, so let's ignore it:
         (clojure-ts-mode . (lambda ()
                              (face-remap-add-relative 'font-lock-number-face 'default))))
  :bind (:map clojure-ts-mode-map
              ("C-("     . clojure-ts-convert-collection-to-list)
              ("C-'"     . clojure-ts-convert-collection-to-quoted-list)
              ("<C-lsb>" . clojure-ts-convert-collection-to-vector)
              ("C-{"     . clojure-ts-convert-collection-to-map)
              ("C-#"     . clojure-ts-convert-collection-to-set)
              (";"       . lispy-comment))
  :config
  (setq clojure-ts-toplevel-inside-comment-form t))
#+end_src

Next, we need [[https://github.com/clojure-emacs/cider][CIDER]], the Clojure Interactive Development Environment that Rocks!

In addition to refactoring keybindings, there are some more keybindings I'd like to add to =cider-mode=.

*cider-selector:*
There's a very handy built-in function called =cider-selector= which lets you type a key to select one of CIDER's buffers, such as the =*cider-error*= and =*cider-scratch*= buffers. But it's bound to the kind of clunky =C-c M-s=, so let's rebind it to the more ergonomic =C-c s=.

Prefix any of the selector commands with =4= to open the buffer in a new window instead of the current one.

*Eval buffer and run all tests in namespace:*
Evaluate the whole namespace and run all its tests in one go!

*Eval and copy to clipboard:*
The wonderful team at the Norwegian Food Authority are Emacs hackers after my own heart. They have some really nice functions for evaluating an sexp after point or top-level form at point and copying it to the clipboard. I want this enhanced experience too!

*Eval def:*
If you've ever def'ed something, you'll know that to pretty print it, you need to either pretty print the form itself, or evaluate the symbol that you def'ed and pretty print its value. In other words, you need to move your cursor to the appropriate place. Again the Norwegian Food Authority team have a handy little function for pretty printing from anywhere within the =def= form!

#+begin_src emacs-lisp
(defun soph/cider-eval-and-test-ns ()
  "Evaluate the current namespace, then run all tests associated with it."
  (interactive)
  (cider-eval-buffer)
  (cider-test-run-ns-tests nil))

;; From https://github.com/magnars/emacsd-reboot/blob/28dcd6e8b0d4f94bcb874fe2c8c3a1f6de4b1bb3/packages/setup-cider.el#L113-L124
(defun soph/cider-eval-to-clipboard ()
  "Evaluate the Clojure form at point and put the result on the clipboard."
  (interactive)
  (let ((form (cider-last-sexp)))
    (cider-nrepl-send-eval-request
     form
     (lambda (response)
       (when (nrepl-dict-get response "value")
         (let ((result (nrepl-dict-get response "value")))
           (kill-new result)
           (message "Result copied to clipboard: %s" result))))
     (cider-current-ns))))

;; From https://github.com/magnars/emacsd-reboot/blob/28dcd6e8b0d4f94bcb874fe2c8c3a1f6de4b1bb3/packages/setup-cider.el#L126-L136
(defun soph/cider-eval-defun-to-clipboard ()
  "Evaluate the current top-level form and copy the result to the clipboard."
  (interactive)
  (cider-nrepl-send-eval-request
   (cider-defun-at-point)
   (lambda (response)
     (when (nrepl-dict-get response "value")
      (let ((result (nrepl-dict-get response "value")))
        (kill-new result)
        (message "Result copied to clipboard: %s" result))))
   (cider-current-ns)))

;; From https://github.com/magnars/emacsd-reboot/blob/e863bb13aab4d284d1648bd6b266c1b6f77109e9/packages/setup-clojure-mode.el#L158-L166
(defun soph/cider-eval-def-symbol ()
  "Evaluate and pretty-print the value of the symbol of the def at point."
  (interactive)
  (save-excursion
    (beginning-of-defun)
    (paredit-forward-down)
    (when (looking-at "def\\(\\w*\\)")
      (paredit-forward 2)
      (cider-pprint-eval-last-sexp))))
#+end_src

And now, finally, to assembly!

CIDER adds support for interactive Clojure programming in Emacs. It provides built-in support for firing up a REPL and looking up documentation and source code, but it also has very Emacs-like shortcuts for expected actions, such as =C-x C-e= to evaluate the s-expression at point.

Let's add some keybindings, including for the functions we defined above.

#+begin_src emacs-lisp
(use-package cider
  :defer t
  :bind (:map cider-repl-mode-map
              ("C-l"   . cider-repl-clear-buffer))
  :bind (:map cider-mode-map
              ("C-c s"     . cider-selector)
              ("C-c t"     . soph/cider-eval-and-test-ns)
              ("C-c C-w"   . soph/cider-eval-to-clipboard)
              ("C-c C-M-w" . soph/cider-eval-defun-to-clipboard)
              ("C-c d"     . soph/cider-eval-def-symbol))
  :config
  (setq cider-repl-display-help-banner       nil
        clojure-toplevel-inside-comment-form t
        cider-download-java-sources          t)
  ; (def-cider-selector-method ?e
  ;   "CIDER result buffer."
  ;   cider-result-buffer)
  )

(add-hook 'cider-repl-mode-hook 'smartparens-mode)
#+end_src

I also use CIDER's scratch buffer quite frequently, but it has its own special mode, which means all my normal refactoring keybindings don't work. This advice makes it enter =clojure-ts-mode= when creating a new scratch buffer:

#+begin_src emacs-lisp
(define-advice cider-scratch--create-buffer (:around (orig-fn name repl) clojure-ts)
  (ignore orig-fn)
  (with-current-buffer (get-buffer-create name)
    (clojure-ts-mode)
    (setq-local sesman-system 'CIDER)
    (local-set-key (kbd "C-j") #'cider-eval-print-last-sexp)
    (cider-scratch--attach repl)
    (cider-scratch--insert-welcome-message)
    (current-buffer)))
#+end_src

 [[https://github.com/clj-kondo/clj-kondo][clj-kondo]] is a linter for Clojure. It even has its own flycheck-mode, [[https://github.com/borkdude/flycheck-clj-kondo][flycheck-clj-kondo]]. We need to install it first.

#+begin_src emacs-lisp
(use-package flycheck-clj-kondo
  :ensure t)
#+end_src

 [[https://github.com/clojure-emacs/clj-refactor.el][clj-refactor]] is a CIDER extension for refactoring.
It can auto-insert the namespace of new files, but LSP mode already does that, so let's disable it.

#+begin_src emacs-lisp
(use-package clj-refactor
  :after clojure-ts-mode
  :hook (clojure-ts-mode . clj-refactor-mode)
  :config
  (setq cljr-add-ns-to-blank-clj-files nil)
  (cljr-add-keybindings-with-prefix "C-<return>"))
#+end_src

[[https://github.com/clojure-emacs/cider-eval-sexp-fu][cider-eval-sexp-fu]] provides small improvements on the default way CIDER evaluates sexpressions. Note that the colour of the flash is changed when switching themes in [[#themes][Themes]], via a hook in =auto-dark=.

#+begin_src emacs-lisp
(use-package eval-sexp-fu
  :after cider)

(use-package cider-eval-sexp-fu
  :after cider)
#+end_src

[[https://github.com/clojure-emacs/sayid/][Sayid]] is a powerful debugger and profiler for Clojure, with a great CIDER plugin.

#+begin_src emacs-lisp
(use-package sayid
  :defer t
  :after cider
  :config
  (with-eval-after-load 'clojure-ts-mode
    (sayid-setup-package)))
#+end_src

[[https://github.com/djblue/portal][Portal]] is a tool for debugging Clojure code. In Clojure, you can "tap" variables, which lets you have a look at their concrete values during execution. Portal lets you tap out stuff to a nice UI where you can collapse and inspect the values. It creates a log of nicely printed stuff.

To use Portal, I have a dev profile called =portal= in my =project.clj= and all I need to do is to jack in with this profile, require Portal, and then go. Let's pull in these snippets borrowed from my colleague [[https://github.com/falcowinkler][Falco Winkler]]. The first lets you interactively choose which profile to jack in with. The second is hardcoded to jack in with the =portal= profile.

#+begin_src emacs-lisp
(defun cider-jack-in-with-profile ()
  (interactive)
  (letrec ((profile (read-string "Enter profile names (,separated): "))
           (lein-params (concat "with-profile +" profile " repl :headless")))
    (message "lein-params set to: %s" lein-params)
    (set-variable 'cider-lein-parameters lein-params)
    (cider-jack-in '())))

(defun cider-jack-in-with-portal-profile ()
  (interactive)
  (set-variable 'cider-lein-parameters "with-profile +portal repl :headless")
  (cider-jack-in '()))
#+end_src

**** Babashka

[[https://babashka.org/][Babashka]] is a scripting runtime for Clojure with super fast startup times.

You can trigger a bunch of useful commands in your project with it, and it plays super nice with [[https://github.com/filipesilva/invoker][Invoker]].

And of course, it's nice to have a way to run Babashka tasks from inside Emacs! This code is from the [[https://github.com/magnars/emacsd-reboot/blob/main/packages/setup-babashka-task-mode.el][Emacs config]] from the team over at Mattilsynet (The Norwegian Food Authority).

#+begin_src emacs-lisp
(defun shorten-path (path)
  "Shortens the file PATH by replacing the home directory with ~."
  (let ((home (expand-file-name "~")))
    (if (string-prefix-p home path)
        (concat "~" (substring path (length home)))
      path)))

(defun babashka-find-tasks ()
  "Find all babashka tasks by running `bb tasks'."
  (let* ((default-directory (projectile-project-root))
         (output (shell-command-to-string "bb tasks 2>/dev/null")))
    (when (and output (not (string-empty-p output)))
      (->> (split-string output "\n" t)
           ;; Skip header line "The following tasks are available:"
           (--filter (not (string-match-p "^The following tasks" it)))
           ;; Parse task names (first word of each line)
           (--map (car (split-string (string-trim it) " " t)))
           ;; Filter out nil/empty
           (--filter (and it (not (string-empty-p it))))))))

(defvar babashka--previous-window-configuration nil)
(defvar babashka--previous-task nil)

(defun babashka-invoke-task (&optional repeat?)
  "Invoke a babashka task interactively.
With REPEAT? non-nil, re-run the previous task without prompting."
  (interactive)
  (let* ((project-root (projectile-project-root))
         (short-dir (shorten-path project-root))
         (default-directory project-root)
         (bb-buffer-name (concat "*Babashka " (projectile-project-name) "*"))
         (prev (if (get-buffer bb-buffer-name)
                   (with-current-buffer bb-buffer-name
                     babashka--previous-window-configuration)
                 (list (current-window-configuration) (point-marker))))
         (tasks (babashka-find-tasks))
         (task (cond
                ;; Repeat previous task
                ((and repeat? (get-buffer bb-buffer-name))
                 (with-current-buffer bb-buffer-name
                   babashka--previous-task))
                ;; No tasks found
                ((null tasks)
                 (user-error "No babashka tasks found in %s" short-dir))
                ;; Prompt user to select
                (t (completing-read (format "bb task in %s: " short-dir)
                                    (--map (concat "bb " it) tasks))))))
    (when task
      (async-shell-command task bb-buffer-name)
      (unless (s-equals? (buffer-name) bb-buffer-name)
        (switch-to-buffer-other-window bb-buffer-name))
      (setq-local babashka--previous-window-configuration prev)
      (setq-local babashka--previous-task task)
      (read-only-mode)
      (local-set-key (kbd "b") 'babashka-invoke-task)
      (local-set-key (kbd "g") (lambda () (interactive) (babashka-invoke-task t)))
      (local-set-key (kbd "q") (lambda ()
                                 (interactive)
                                 (let ((conf babashka--previous-window-configuration))
                                   (kill-buffer)
                                   (when conf (register-val-jump-to conf nil))))))))

(with-eval-after-load 'clojure-ts-mode
  (define-key clojure-ts-mode-map (kbd "M-b") 'babashka-invoke-task))
#+end_src

*** Emacs Lisp

First, we need the holy trinity of Elisp libraries: [[https://github.com/magnars/dash.el][dash]] (lists), [[https://github.com/magnars/s.el][s]] (strings), and [[https://github.com/rejeep/f.el][f]] (files).

#+begin_src emacs-lisp
(use-package dash
  :defer t)

(use-package s
  :defer t)

(use-package f
  :defer t)
#+end_src

Next, let's take a page out of CIDER's book and enhance evaluation of Emacs Lisp by adding [[https://github.com/xiongtx/eros][Eros]] (Evaluation Result OverlayS). Also, I'm so used to the CIDER bindings, so let's bind some CIDER-like bindings for elisp as well.

#+begin_src emacs-lisp
(use-package eros
  :defer t
  :functions
  eros-mode
  eros-eval-defun
  :bind (:map emacs-lisp-mode-map
              ("C-c C-c" . eros-eval-defun)
              ("C-c C-k" . eval-buffer))
  :config
  (eros-mode 1))
#+end_src

*** Scheme

Your favourite flavour of Scheme is probably supported by a version of [[https://github.com/emacsmirror/geiser][Geiser]].
This is for the MIT/GNU flavour:

#+begin_src emacs-lisp
(use-package geiser-mit
  :defer t)
#+end_src

*** Haskell

 For Haskell, I think the regular =haskell-mode= is nice. I'll add =haskell-doc-mode=
 which uses eldoc consistently throughout.

 I also want to use the tool [[https://github.com/ndmitchell/hoogle][Hoogle]] from directly within Emacs to quickly
 look up functions and packages. I've set it up according to the GitHub docs, so
 that =C-c h= opens a prompt and querying the database opens a help buffer inside
 Emacs with the results.

 #+BEGIN_SRC emacs-lisp
 (use-package haskell-mode
   :defer t
   :hook (haskell-mode . haskell-doc-mode)
   :config
   (setq haskell-hoogle-command                  "hoogle"
         haskell-compile-stack-build-command     "stack build"
         haskell-compile-stack-build-alt-command "stack build --pedantic"
         haskell-compile-command                 "stack build")
   :bind (:map haskell-mode-map
               ("C-c C-h" . haskell-hoogle)
               ("C-c C-c" . haskell-compile)))
 #+END_SRC

[[https://github.com/jyp/attrap][attrap]] is a package that ATempts To Repair At Point. I'll bind the function to fix the thing at point to =C-c C-f= for "please, FIX!"

#+begin_src emacs-lisp
(use-package attrap
  :defer t
  :bind ("C-c C-f" . attrap-attrap))
#+end_src

*** Agda

 To install Agda, you need Haskell - stack or cabal - and a few other
 programs. Once those are installed, you can add this to your =init.el=.
 Or you can just let =agda-mode setup= do it for you.

 #+begin_src emacs-lisp :tangle no
 (load-file (let ((coding-system-for-read 'utf-8))
                 (shell-command-to-string "agda-mode locate")))
 #+end_src

*** OCaml

 OCaml s some setup for =ocp-indent=,

 #+begin_src emacs-lisp :tangle no
 (use-package ocp-indent
   :defer t)
 #+end_src

 and for =merlin=.
 
 #+begin_src emacs-lisp
 (let ((opam-share (ignore-errors (car (process-lines "opam" "var" "share")))))
       (when (and opam-share (file-directory-p opam-share))
		;; Register Merlin
		(add-to-list 'load-path (expand-file-name "emacs/site-lisp" opam-share))
		(autoload 'merlin-mode "merlin" nil t nil)
		;; Automatically start it in OCaml buffers
		(add-hook 'tuareg-mode-hook 'merlin-mode t)
		(add-hook 'caml-mode-hook 'merlin-mode t)
		;; Use opam switch to lookup ocamlmerlin binary
		(setq merlin-command 'opam)))
 #+end_src

 Then I want integration with Dune, Merlin, and utop for the full IDE-experience.

 #+begin_src emacs-lisp
 ;; Major mode for OCaml programming
 (use-package tuareg
   :defer t
   :mode (("\\.ocamlinit\\'" . tuareg-mode)))

 ;; Major mode for editing Dune project files
 (use-package dune
   :defer t)

 ;; Merlin provides advanced IDE features
 (use-package merlin
   :defer t
   :config
   (add-hook 'tuareg-mode-hook #'merlin-mode)
   ;; we're using flycheck instead
   (setq merlin-error-after-save nil))

 (use-package merlin-eldoc
   :defer t
   :hook ((tuareg-mode) . merlin-eldoc-setup))

 ;; utop REPL configuration
 (use-package utop
   :defer t
   :config
   (add-hook 'tuareg-mode-hook #'utop-minor-mode))
 #+end_src

*** Python

[[https://github.com/jorgenschaefer/elpy][elpy]] is a Emacs Python Development Environment with batteries included.

#+begin_src emacs-lisp
(use-package elpy
  :defer t
  :init
  (elpy-enable)
  :config
  (setq python-shell-interpreter "python"
        python-shell-interpreter-args "-i"))
#+end_src

#+begin_src emacs-lisp
(use-package blacken
  :after elpy)
#+end_src

*** JavaScript/TypeScript

I use TypeScript at work sometimes, but I don't know a lot about it. Luckily, my colleagues [[https://github.com/mbezjak/dotfiles/blob/6e80e47920f0a5143b7ef89d8d9b1e644b5a20aa/emacs.d/lisp/init.el#L536-L582][Miro]] and [[https://github.com/slipset/emacsd/blob/8f31789dbb516ddd7486acb55f9ea48401267df3/init.el#L135-L167][Erik]] have JS/TS setups in their Emacs configs, so I could just steal from people who know what they're doing.

In addition to this, I have LSP mode for TypeScript, autoformatting via Prettier, and the TypeScript tree-sitter grammar installed.

#+begin_src emacs-lisp
(use-package rjsx-mode
  :defer t
  :mode "components\\/.*\\.js\\'")
#+end_src

Of course, we need code formatting. At work, we use [[https://prettier.io/][Prettier]], so let's pull in an Emacs package for that, like [[https://github.com/prettier/prettier-emacs][pretties-js]].

#+begin_src emacs-lisp
(use-package prettier-js
  :hook ((typescript-ts-mode . prettier-js-mode)
         (tsx-ts-mode . prettier-js-mode)))
#+end_src

In CIDER (for Clojure), I have keybindings to jump to a reference and to jump back. [[https://github.com/js-emacs/xref-js2][xref-js2]] gives me the same capabilities for JS/TS, and with a bit of config, my muscle memory will serve me well in frontend-land as well.

#+begin_src emacs-lisp
(use-package xref-js2
  :defer t
  :config
  (define-key js2-mode-map (kbd "M-.") nil)
  (add-hook 'js2-mode-hook
            (lambda () (add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t)))
  (setq xref-js2-search-program 'rg)
  (define-key js2-mode-map (kbd "M-.") 'xref-find-definitions)
  (define-key js2-mode-map (kbd "M-,") 'xref-pop-marker-stack))
#+end_src

It's also nice to have the same keybindings for running tests in TypeScript as I already do in Clojure via CIDER. [[https://github.com/rymndhng/jest-test-mode][jest-test-mode]] comes with CIDER-style bindings out of the box. From the project's README:

| Binding     | Action                                                           |
|-------------+------------------------------------------------------------------|
| =C-c C-t n=   | Runs tests in the current buffer.                                |
| =C-c C-t p=   | Runs all tests in the project.                                   |
| =C-c C-t t=   | Runs the it/test/describe block at the current point.            |
| =C-c C-t a=   | Re-runs the previous test command.                               |
| =C-c C-t d n= | Runs tests in the current buffer with node debugger.             |
| =C-c C-t d t= | Runs the describe block at the current point with node debugger. |
| =C-c C-t d a= | Re-runs the previous test command with node debugger.            |


#+begin_src emacs-lisp
(use-package jest-test-mode
  :defer t
  :commands jest-test-mode
  :hook (typescript-ts-mode typescript-mode typescript-tsx-mode js-mode)
  :config
  (setq jest-test-command-string "npx %s vitest %s %s"))
#+end_src

It's also nice to have a small util for typechecking:

#+begin_src emacs-lisp
(defun run-typescript-check ()
  (interactive)
  (compile "pnpm check-types"))

(add-hook 'typescript-ts-mode-hook
          (lambda ()
            (local-set-key (kbd "C-c C-t c") 'run-typescript-check)))
#+end_src

* Activating Custom Keybindings
*** Extra Keybindings

Most of my custom keybindings are bound directly in the section with the
relevant package, but here are a few extra ones.

Switch to the other window  =C-x o= \to  =M-o=.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "M-o") 'other-window)
#+end_src

I also have both =C-x b= and =C-x C-b= bound to =consult-buffer=.
Sometimes, though, it's nice to have a dedicated =*Ibuffer*= window. Let's bind it to =C-c b=.

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-c b") 'ibuffer)
#+end_src

In my lispy+paredit keybindings, I have =C-f= and =C-b= bound to =forward-= and =backward-sexp=, respectively. That's because I use the arrow keys when I want to move one char at a time, and the bindings =C-f/-b= are so much more ergonomic to use than =C-M-f/-b=. So let's do that everywhere!

#+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-f") 'forward-sexp)
(define-key custom-bindings-map (kbd "C-b") 'backward-sexp)
#+end_src

I haven't bound super to anything on my work's Mac, so the default =s-u= for reverting buffers doesn't work very well for me. I'll add another binding so I can keep using only control and meta.

 #+begin_src emacs-lisp
(define-key custom-bindings-map (kbd "C-c u") 'revert-buffer)
 #+end_src

*** Activating the Keymap

Throughout the configuration, I've added bindings to my custom-bindings-map.
The last thing we need to to before we can call it a day, is to define a minor
mode for it and activate that mode. The below code does just that.

#+begin_src emacs-lisp
(define-minor-mode custom-bindings-mode
  "A mode that activates custom keybindings."
  :init-value t
  :keymap custom-bindings-map)
#+end_src

* TODOs
- [ ]  Figure out nice way to search PDFs via pdf-tools (nicer than ISearch, preferrably something consult-related)
- [ ]  Check out [[https://github.com/oantolin/embark][embark]]
- [ ]  Check out [[https://github.com/otavioschwanck/harpoon.el][harpoon]]
- [ ]  Check out [[https://github.com/alphapapa/bufler.el][bufler]]
- [ ]  Configure Magit [[https://github.com/magit/forge][Forge]]
- [ ]  Check out [[https://github.com/kickingvegas/casual-suite][casual-suite]] (especially for IBuffer and Dired)
- [ ]  Check out [[https://github.com/emacscollective/no-littering][no-littering]]
- [ ]  Check out [[https://github.com/ISouthRain/emsg-blame][emsg-blame]]
- [ ]  Check out [[https://github.com/gilbertw1/better-jumper][better-jumper]]
- [ ]  Check out [[https://github.com/projectional-haskell/structured-haskell-mode][Structured Haskell Mode]]
- [ ]  Check out [[https://github.com/casouri/eldoc-box#prettify-typescript-error-message][eldoc-box]] (especially for TS/JS errors)

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

config
configuration
emacs

Contributors

SophieBosio

866 commits

sakh1979

4 commits

Languages

Emacs Lisp

100.0%