Skip to content

quarkQuark/emacs-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 

Repository files navigation

Emacs configuration

The best place to read this file is at https://quarkQuark.github.io/literate-config/emacs. The org-mode source is stored at https://github.com/quarkQuark/emacs-config.

Contents

Installation guide

Emacs expects to find user configuration in .config/emacs/init.el. This literate configuration file must be tangled in order to generate that file and apply the configuration. Once Emacs is installed, change to the same directory as this file and run

emacs --batch -l org --eval '(org-babel-tangle-file README.org)'

Make sure to also download the fonts Fira Code, ET Book (known to Emacs as ETBembo) and Symbola:

sudo pacman -S ttf-fira-code
yay -S ttf-et-book ttf-symbola

When Emacs is launched for the first time, it will spend some time downloading and installing various packages. To install necessary symbol fonts run M-x all-the-icons-install-fonts.

Set-up

Early Init

Avoid garbage collection at startup (the threshold must be raised back to a reasonable level afterwards). When I implemented this, the number of garbage collections on startup went down from 39 to 2 and the startup time went down from 1.4 to 0.83 seconds.

(setq gc-cons-threshold most-positive-fixnum
      gc-cons-percentage 0.6)

Set XDG directory locations

;; Macro to give fallback if no environment variables.
;; From http://technical-hobbyist.blogspot.com/2014/01/xdg-directories-for-emacs.html

(defvar user-emacs-config-directory nil
  "Directory containing Emacs user configuration files.")
(defvar user-emacs-data-directory nil
  "Directory containing Emacs user data and Lisp files. This also sets \\[user-emacs-directory].")
(defvar user-emacs-cache-directory nil
  "Directory containing Emacs cache files which can be deleted without loss of information.")

(require 'cl-lib)
(cl-macrolet ((setq-if-null (variable value)
                         `(if (null ,variable)
                              (setf ,variable ,value)))
           (getdir (variable fallback)
                   `(expand-file-name "emacs/" (or (getenv ,variable) ,fallback))))
  (setq-if-null user-emacs-config-directory (getdir "XDG_CONFIG_HOME" "~/.config/"))
  (setq-if-null user-emacs-data-directory (getdir "XDG_DATA_HOME" "~/.local/share/"))
  (setq-if-null user-emacs-cache-directory (getdir "XDG_CACHE_HOME" "~/.cache/")))

(setf user-emacs-directory user-emacs-data-directory)
(setq package-user-dir (expand-file-name "packages" user-emacs-data-directory))

If possible (version >= 29), store native compiltion binaries in XDG location.

(when (fboundp 'startup-redirect-eln-cache)
  (startup-redirect-eln-cache
   (convert-standard-filename
    (expand-file-name  "var/eln-cache/" user-emacs-directory))))

Stop unwanted UI elements from loading

(setq-default frame-inhibit-implied-resize t
              ;inhibit-startup-screen t
              initial-scratch-message nil)

;; See https://github.com/mclear-tools/dotemacs/blob/master/early-init.el
(push '(tool-bar-lines . 0)   default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
;(push '(undecorated . t)      default-frame-alist)
(customize-set-variable 'tool-bar-mode nil)
(customize-set-variable 'scroll-bar-mode nil)
(defun display-startup-echo-area-message () (message ""))

;; Stop a split from appearing on nativecomp warnings
(setq warning-minimum-level :error)

Package management

Do not use package.el, because I use straight.el instead

(setq package-enable-at-startup nil)

Update with (straight-pull-all) and apply changes by restarting Emacs. View installed with M-x straight-pull-package and available recipes with M-x straight-use-package. Delete unused packages with (straight-remove-unused-repos).

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 6))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))
(straight-use-package 'use-package)
(setq straight-use-package-by-default t
      straight-vc-git-default-protocol 'ssh)

No-littering.el

(use-package no-littering)

(let ((autosave-dir (no-littering-expand-var-file-name "auto-save/")))
  (make-directory autosave-dir t)
  (setq auto-save-file-name-transforms `((".*" ,autosave-dir t)))
        custom-file (expand-file-name "custom.el" user-emacs-directory))

Utility functions and variables

(defvar quark/work-computer-p (file-directory-p "~/Work")
  "Check if this is my work computer.")

(defvar quark/work-dir (if quark/work-computer-p "~/Work" "~")
  "Directory containing work-related files.")

User Interface

(scroll-bar-mode -1)    ; Disable scrollbar.
(tool-bar-mode -1)      ; Disable toolbar.
(menu-bar-mode -1)      ; Disable menu bar.
(tooltip-mode -1)       ; Disable tooltips.
(set-fringe-mode 10)    ; Add breathing room.
(column-number-mode)    ; Show column number in modeline.
(size-indication-mode)  ; Show file size in modeline.
(when (>= emacs-major-version 29)
    ; Smooth scrolling.
    (pixel-scroll-precision-mode)
    ; Or 'child-frame. Not certain of the difference.
    (setq show-paren-context-when-offscreen 'overlay))

;; Scrolls way too fast by default.
(setq mouse-wheel-scroll-amount '(1 ((control) . 'text-scale))
      scroll-margin 3
      scroll-step 1)

(use-package all-the-icons)
(use-package minions)
(use-package doom-modeline
  :init
  (doom-modeline-mode)
  (minions-mode)
  :custom
  (doom-modeline-buffer-encoding nil)
  (doom-modeline-minor-modes t))

;; Pop-up to make keybindings discoverable.
(use-package which-key
  :init (which-key-mode)
  :custom (which-key-idle-delay 0.3))

;; Preview colour codes
(use-package rainbow-mode
  :hook
  (tex-mode  . rainbow-mode)
  (html-mode . rainbow-mode)
  (css-mode  . rainbow-mode)
  :config (rainbow-mode))

Theme

Enabling Solaire mode slightly dims the backgrounds of non-editing buffers. This draws the eye to the editing space and gives the window a more refined look. Themes need to be configured to work with this. The package doom-themes contains many nice preconfigured themes.

(use-package solaire-mode
  :config (solaire-global-mode))

(use-package doom-themes
  :config (doom-themes-org-config))

Liked dark themes:

  • doom-vibrant (default Doom Emacs theme, with slightly more contrast).
  • doom-nord (Nord. Rather low contrast).
  • doom-palenight (Used by System Crafters. Elegant and slightly purple).

Liked light themes:

  • doom-one-light (Default Doom Emacs light theme).
(load-theme 'doom-one-light t)

(use-package ligature
  :config
  (ligature-set-ligatures '(prog-mode org-mode)
                          '("www" "\\\\" "\\\\\\" "//" "///"
                            "<|" "<|>" "|>" "<>" "</" "</>" "/>"
                            "<+" "+>" "<+>" "<*" "<*>" "*>" "<$" "<$>" "$>"
                            "**" "***" "*/" "/*" "**/" "/**"
                            "!!" "??" "%%" "&&" "||" "||=" "|=" ";;" "::" ":::" "~@" "++" "+++"
                            "?=" "^=" ":=" "=:=" "==" "===" "!=" "=/=" "!==" "/=" "/=="
                            "----"
                            "<!--"
                            "<--" "<-" "<->" "->" "-->"
                            "-<" "<<-" "-<<" ">>-" "->>" ">-"
                            "<=" ">="
                            "<<<" "<<=" "<<" ">>" ">>=" ">>>"
                            "<=<" "<==" "<=>" "==>" ">=>"
                            "=>" "=>>"
                            "=<<"
                            "<~~" "<~" "~~" "~>" "~~>"
                            "#{" "#[" "##" "###" "####" "#(" "#?" "#_" "#_(" "{-" "-}"
                            ".-" ".=" ".." "..<" "..."))
  (global-ligature-mode t))
  (use-package dash)

  ;; From Emacs wiki
  (defun font-candidate (&rest fonts)
    "Return the first available font."
    (--first (find-font (font-spec :name it)) fonts))

(set-face-attribute 'default        nil :font "Fira Code Retina" :height 101)
(set-face-attribute 'fixed-pitch    nil :font "Fira Code Retina" :height 101)
(set-face-attribute 'variable-pitch nil
                    :font (font-candidate "ETBembo" "Georgia" "DejaVu Sans")
                    :height 120)
(when (member "Symbola" (font-family-list))
  (set-fontset-font t 'symbol (font-spec :family "Symbola") nil 'prepend))

;; Contrast with font-lock-comment-face, which is base4 on
(set-face-attribute 'font-lock-doc-face nil :inherit 'font-lock-comment-face
                    :background (doom-color 'bg-alt)
                    :foreground (doom-darken (doom-color 'base4) 0.3)
                    :extend t)

Keybindings and Evil Mode

(use-package general
  :config
  (general-evil-setup)
  (general-create-definer my-leader-def       :states 'normal :prefix "SPC")
  (general-create-definer my-local-leader-def :states 'normal :prefix "SPC m")
  (my-leader-def
    "b" 'counsel-switch-buffer
    "f" '(:ignore t :which-key "files")
    "ff" 'find-file))

(defun quark/load-dotfile ()
  "Reload Emacs configuration."
  (interactive)
  (load-file (expand-file-name "~/.config/emacs/init.el")))

(my-leader-def
  "e" '(:ignore t :which-key "eval")
  "eb" 'eval-buffer
  "ed" 'quark/load-dotfile
  "ee" 'eval-last-sexp)

(general-def
  "C-=" `text-scale-increase
  "C--" `text-scale-decrease)

;; Required for C-r (evil-mode undo).
(use-package undo-tree
  :init (global-undo-tree-mode))

(use-package evil
  :init
  (setq evil-move-cursor-back nil
        evil-want-keybinding nil ;; For evil-collection.
        evil-want-Y-yank-to-eol 1
        evil-undo-system 'undo-tree)
  :config
  (evil-mode)
  (general-def "<escape>" 'keyboard-escape-quit) ; Make ESC quit prompts.
  (general-def evil-insert-state-map "C-g" 'evil-normal-state)
  (general-def 'normal "j" 'evil-next-visual-line)
  (general-def 'normal "k" 'evil-previous-visual-line))

(use-package evil-collection
  :after evil
  :config (evil-collection-init))

;; Escape insert mode with "jk".
(general-imap "j" (general-key-dispatch 'self-insert-command
                    :timeout 0.25
                    "k" 'evil-normal-state))

(use-package evil-numbers)

Ivy (completion)

(use-package ivy
  :bind
  (("C-s" . swiper)
   :map ivy-minibuffer-map
   ("TAB" . ivy-alt-done)
   ("C-l" . ivy-alt-done)
   ("C-j" . ivy-next-line)
   ("C-k" . ivy-previous-line)
   :map ivy-switch-buffer-map
   ("C-k" . ivy-previous-line)
   ("C-l" . ivy-done)
   ("C-d" . ivy-switch-buffer-kill)
   :map ivy-reverse-i-search-map
   ("C-k" . ivy-previous-line)
   ("C-d" . ivy-reverse-i-search-kill))
  :custom
  (ivy-extra-directories nil)
  :config (ivy-mode))

(use-package ivy-rich
  :after ivy
  :init (ivy-rich-mode))

(use-package counsel
  :custom
  ;; Don't show hidden files unless matched
  (counsel-find-file-ignore-regexp "\\`\\.")
  :config (counsel-mode))

Interactively open dotfiles

This command (bound to SPC f d) allows me to search for and open dotfiles.

(defun quark/ivy-find-file-action (key)
  "Find dotfile from KEY in quark/dotfile-list."
  (with-ivy-window (find-file (cdr (assoc key quark/dotfile-list)))))

;; A list of dotfiles, each associated with a keyword.
(setq quark/dotfile-list
      '(("Dotfiles README" . "~/README.org")
        ("Shells"          . "~/.config/Shells.org")
        ("Emacs"           . "~/Projects/emacs-config/README.org")
        ("XMonad"          . "~/Projects/xmonad-quark/README.org")))

(defun quark/ivy-open-dotfile ()
  "Open configuration file for PROGRAM."
  (interactive)
  (ivy-read "Open dotfile: " (mapcar 'car quark/dotfile-list)
            :action 'quark/ivy-find-file-action))

(my-leader-def "fd" 'quark/ivy-open-dotfile)

Programming

(setq-default indent-tabs-mode nil)

(defun quark/display-line-numbers ()
  "Set up line numbers locally according to my preference."
  (setq-local display-line-numbers 'visual
              display-line-numbers-current-absolute t))

(add-hook 'prog-mode-hook 'quark/display-line-numbers)

;; More easily see how parentheses pair up.
(use-package rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

;; Automatically close parentheses.
(use-package smartparens
  :hook
  (prog-mode . smartparens-mode)
  ;(text-mode . smartparens-mode)
  :config (require 'smartparens-config))

(use-package lsp-mode
  :commands (lsp lsp-deferred)
  :custom (lsp-keymap-prefix "C-l")
  ;:config (lsp-enable-which-key-integration t) ; Variable seems to not exist
  )

(use-package lsp-ui
  :hook (lsp-mode . lsp-ui-mode)
  :custom
  (lsp-ui-sideline-show-code-actions nil)
  (lsp-ui-sideline-show-symbol nil)
  (lsp-ui-doc-show-with-cursor t)
  (lsp-ui-doc-show-with-mouse nil)
  :config
  (defun lsp-ui-sideline--compute-height nil '(height unspecified)))

(use-package company
  :hook
  (lsp-mode . company-mode)
  (css-mode . company-mode)
  :bind
  (:map company-active-map ("TAB" . company-complete-selection))
  ;(:map lsp-mode-map ("<tab>" . company-indent-or-complete-common))
  (:map company-active-map ("RET" . nil))
  (:map company-active-map ("<return>" . nil))
  :custom
  (company-minimum-prefix-length 1)
  (company-idle-delay 0.0))

;; Make the ui slightly nicer (I'm not entirely sure if it's worth it).
(use-package company-box
  :hook (company-mode-hook . company-box-mode))

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

(use-package tree-sitter
  :config (global-tree-sitter-mode)
  :hook (tree-sitter-after-on . tree-sitter-hl-mode))
(use-package tree-sitter-langs
  :after tree-sitter)

(use-package dap-mode)

Emacs Lisp

;; Be strict about parentheses
(add-hook 'emacs-lisp-mode-hook 'smartparens-strict-mode)
(use-package evil-cleverparens
  :hook (emacs-lisp-mode . evil-cleverparens-mode))

;; Make emacs regex more readable
(use-package easy-escape
  :hook (emacs-lisp-mode . easy-escape-minor-mode))

;; Highlight symbols defined by the user
;; (use-package highlight-defined
;;   :hook (emacs-lisp-mode . highlight-defined-mode))

Haskell

Lsp-mode requires haskell-language-server. If running Arch Linux, make sure to install aur/haskell-language-server-static from instead of community/haskell-language-server. The Arch official repositories use dynamic linking for Haskell packages, which leads to many messy dependency problems when installing or upgrading any Haskell package.

(use-package yaml-mode)
(use-package haskell-mode
  :config
  (add-hook 'haskell-mode-hook 'interactive-haskell-mode)
  :custom
  (haskell-process-show-debug-tips nil)
  (haskell-process-log t)
  (haskell-svg-render-images t))
(use-package lsp-haskell
  :hook
  (haskell-mode . lsp-deferred)
  (lsp-mode
   . (lambda ()
       (lsp-register-custom-settings
        ;; Allows use of fourmolu.yaml
        '(("haskell.plugin.fourmolu.config.external" t t)))))
  :custom
  (lsp-haskell-formatting-provider "fourmolu"))

Python

; Needs pip install python-lsp-server pylsp-rope ruff-lsp python-lsp-black debugpy
; Install pylsp-mypy with pip, or with pipenv if in a virtualenv
(use-package python
  :straight nil ; python-mode is built-in
  :hook (python-mode . lsp-deferred)
  :custom
  (lsp-pylsp-plugins-black-enabled t)
  (lsp-ruff-lsp-ruff-args ["--select=ALL" "--ignore=ARG,ANN,D,ERA001,T20"])
  (dap-python-debugger 'debugpy)
  ;; Disabled to prevent duplicate messages
  (lsp-pylsp-plugins-mccabe-enabled nil)
  (lsp-pylsp-plugins-pydocstyle-enabled nil)
  :config
  (require 'dap-python))

(use-package kivy-mode)

TypeScript

(defun quark/setup-dap-node ()
  "Load dap-node and run `dap-node-setup' if necessary"
  (require 'dap-node)
  (unless (file-exists-p dap-node-debug-path) (dap-node-setup)))

(use-package typescript-mode
  :hook
  (typescript-mode . lsp-deferred)
  (typescript-mode . quark/setup-dap-node)
  (javascript-mode . lsp-deferred)
  (javascript-mode . quark/setup-dap-node))

CSS

(use-package com-css-sort
  :commands
  (com-css-sort-attributes-block com-css-sort-attributes-document)
  :bind (:map css-mode-map))

;; Requires npm -g install js-beautify
(use-package web-beautify
  :after (css-mode)
  :bind (:map css-mode-map ("C-c b" . web-beautify-css)))

;; Requires npm -g install colorguard
(use-package flycheck
  :hook (css-mode . flycheck-mode))
(use-package flycheck-css-colorguard
  :hook (flycheck-mode . flycheck-css-colorguard-setup)
  :custom (flycheck-css-colorguard-threshold "1"))

Origami enables code-folding. This package is currently disabled as it throws a warning for using the deprecated cl.

;(use-package origami
  ;:hook
  ;(prog-mode . origami-mode)
  ;;(css-mode  . origami-close-all-nodes)
  ;:general
  ;(:keymaps 'evil-normal-state-map
            ;"TAB" 'origami-recursively-toggle-node)
  ;:config
  ;(add-to-list 'origami-parser-alist '(css-mode . origami-c-style-parser)))

Writing

(add-hook 'text-mode-hook 'visual-line-mode)

;(defun quark/visual-fill ()
;  "Configure `visual-fill-column-mode' for `org-mode'."
;  (setq visual-fill-column-width 100
;        visual-fill-column-center-text t)
;  (visual-fill-column-mode))

(use-package visual-fill-column
  :custom
  (visual-fill-column-width 110)
  (visual-fill-column-center-text t)
  (visual-fill-column-enable-sensible-window-split t)
  :hook (text-mode . visual-fill-column-mode)
  :config
  (advice-add 'text-scale-adjust :after 'visual-fill-column-adjust))

Org Mode

https://zzamboni.org/post/beautifying-org-mode-in-emacs/

Setup

Font setup

(defun quark/org-font-setup ()
  "Set up my font preferences for `org-mode'."

  ;; Set heading font sizes.
  (dolist (level (number-sequence 1 8))
    (set-face-attribute
     (intern (format "org-level-%s" level)) nil
     :inherit (intern (format "outline-%s" level))
     :weight 'medium
     :height (cl-case level (1 1.75) (2 1.5) (3 1.25) (4 1.1) (t 1))))  ;; Font size per level

  ;; Customise other faces
  (set-face-attribute 'org-document-title   nil :inherit 'variable-pitch :weight 'bold :height 2.0)
  (set-face-attribute 'org-block            nil :foreground nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-block-begin-line nil :foreground "#aaaaaa" :inherit 'org-block)
  (set-face-attribute 'org-checkbox         nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-code             nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-indent           nil :inherit '(org-hide fixed-pitch))
  (set-face-attribute 'org-formula          nil :inherit 'fixed-pitch)
  (set-face-attribute 'org-meta-line        nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-special-keyword  nil :inherit '(font-lock-comment-face fixed-pitch))
  (set-face-attribute 'org-table            nil :inherit '(shadow fixed-pitch))
  (set-face-attribute 'org-verbatim         nil :inherit '(shadow fixed-pitch)))

Load org-mode

(defun quark/org-mode-setup ()
  "Function to run on `org-mode' startup."
  (variable-pitch-mode)
  (quark/org-font-setup)
  (setq evil-auto-indent nil))

(use-package org
  :commands (org-capture org-agenda)
  :hook (org-mode . quark/org-mode-setup)
  :custom
  ;(org-ellipsis " ▾")
  (org-hide-emphasis-markers t)
  (org-startup-indented t)
  ;(org-startup-with-latex-preview t)
  (org-export-with-smart-quotes t)
  (user-full-name "quarkQuark")
  (org-export-default-language "en-gb")
  :config
  ;; Error =Invalid face= on 'default face.
  ;(plist-put org-format-latex-options :foreground 'auto)
  ;(plist-put org-format-latex-options :background 'auto)
  ;(plist-put org-format-latex-options :scale 1.2)
  )

(use-package toc-org
  :hook
  (org-mode      . toc-org-mode)
  (markdown-mode . toc-org-mode))
  ;; Doesn't seem to be able to find markdown-mode-map unless markdown-mode is loaded
  ;:bind
  ;(:map markdown-mode-map
  ;      ("C-c C-o" . toc-org-markdown-follow-thing-at-point)))

Locations

(setq org-directory "~/Org"
      org-default-notes-file (concat org-directory "/notes.org"))

Keybindings

(general-def :prefix "C-c"
  "l" 'org-store-link
  "a" 'org-agenda
  "c" 'org-capture)

Aesthetics

(use-package org-superstar
  :after org
  :hook (org-mode . org-superstar-mode)
  :custom
  (org-superstar-headline-bullets-list '("" "" "" "" ""))
  (org-superstar-item-bullet-alist '((42 . "*") (43 . "") (45 . "")))
  :config
  (unless (font-candidate "ETBembo")  ;; Georgia doesn't have the right symbols
    (set-face-attribute 'org-superstar-header-bullet nil :family "Fira Code Retina")))

;; Unhide emphasis markers under cursor.
(use-package org-appear
  :hook (org-mode . org-appear-mode))

;; Enable hiding individual src blocks by default.
;; From https://emacs.stackexchange.com/questions/44914/choose-individual-startup-visibility-of-org-modes-source-blocks
(defun individual-visibility-source-blocks ()
  "Fold some blocks in the current buffer."
  (interactive)
  (org-show-block-all)
  (org-block-map
   (lambda ()
     (let ((case-fold-search t))
       (when (and
              (save-excursion
                (beginning-of-line 1)
                (looking-at org-block-regexp))
              (cl-assoc
               ':hidden
               (cl-third
                (org-babel-get-src-block-info))))
         (org-hide-block-toggle))))))
(add-hook
 'org-mode-hook
 (function individual-visibility-source-blocks))

Specify font face for certain special blocks

Org-mode does not expose an option for font-locking of individual special blocks, so we modify the package’s source code with el-patch.

(use-package el-patch)

;; Tell el-patch that we are going to patch org.el.
(el-patch-feature org)

We want to add the following rules for fontifying special blocks:

;; To identify a colour from xcolor in latex, add \begin{testcolors}\testcolor{myCol}\end{testcolors}

(defface org-custom-special-block-definition
  '((((class color) (background light))
     (:background "#ebf1fd" :extend t))
    (((class color) (background dark))
     (:background "#28303a" :extend t)))
  "Face for definition blocks in `org-mode'.")

(defface org-custom-special-block-lemma
  '((default (:slant italic)))
  "Face for lemma blocks in `org-mode'.")

(defface org-custom-special-block-proposition
  '((((class color) (background light))
     (:background "#fff2e5" :extend t))
    ;; (((class color) (background dark))
    ;;  (:background "#282815" :extend t))
    )
  "Face for proposition blocks in `org-mode'.")

(defface org-custom-special-block-theorem
  '((((class color) (background light))
     (:background "#ffffe5" :extend t))
    (((class color) (background dark))
     (:background "#282815" :extend t)))
  "Face for theorem blocks in `org-mode'.")

(defface org-custom-special-block-proof
  '((((class color) (background light))
     (:foreground "#50a14f" :extend t :inherit 'fixed-pitch))
    (((class color) (background dark))
     (:foreground "#7bc275" :extend t :inherit 'fixed-pitch)))
  "Face for proof blocks in `org-mode'.")

(defface org-custom-special-block-eg
  '((((class color) (background light))
     (:foreground "#4078f2" :extend t))
    (((class color) (background dark))
     (:foreground "#51afef" :extend t)))
  "Face for proof blocks in `org-mode'.")

(setq quark/org-fontify-custom-special-block-alist
      '(("definition"  . 'org-custom-special-block-definition)
        ("lemma"       . 'org-custom-special-block-lemma)
        ("proposition" . 'org-custom-special-block-proposition)
        ("theorem"     . 'org-custom-special-block-theorem)
        ("corollary"   . 'org-custom-special-block-lemma)
        ("proof"       . 'org-custom-special-block-proof)
        ("eg"          . 'org-custom-special-block-eg)))

(defun quark/org-fontify-custom-special-block-face (block-type)
  "Fontify `org-mode' custom special blocks."
  (cdr (assoc block-type quark/org-fontify-custom-special-block-alist)))

The following source block contains a copy of the (quite long!) full definition of the function org-fontify-meta-lines-and-blocks-1 with a record of how el-patch should patch it to add our new rules.

(with-eval-after-load 'org
  (el-patch-defun org-fontify-meta-lines-and-blocks-1 (limit)
    "Fontify #+ lines and blocks."
    (let ((case-fold-search t))
      (when (re-search-forward
             (rx bol (group (zero-or-more (any " \t")) "#"
                            (group (group (or (seq "+" (one-or-more (any "a-zA-Z")) (optional ":"))
                                              (any " \t")
                                              eol))
                                   (optional (group "_" (group (one-or-more (any "a-zA-Z"))))))
                            (zero-or-more (any " \t"))
                            (group (group (zero-or-more (not (any " \t\n"))))
                                   (zero-or-more (any " \t"))
                                   (group (zero-or-more any)))))
             limit t)
        (let ((beg (match-beginning 0))
              (end-of-beginline (match-end 0))
              ;; Including \n at end of #+begin line will include \n
              ;; after the end of block content.
              (block-start (match-end 0))
              (block-end nil)
              (lang (match-string 7)) ; The language, if it is a source block.
              (bol-after-beginline (line-beginning-position 2))
              (dc1 (downcase (match-string 2)))
              (dc3 (downcase (match-string 3)))
              (whole-blockline org-fontify-whole-block-delimiter-line)
              beg-of-endline end-of-endline nl-before-endline quoting block-type)
          (cond
           ((and (match-end 4) (equal dc3 "+begin"))
            ;; Truly a block
            (setq block-type (downcase (match-string 5))
                  ;; Src, example, export, maybe more.
                  quoting (member block-type org-protecting-blocks))
            (when (re-search-forward
                   (rx-to-string `(group bol (or (seq (one-or-more "*") space)
                                                 (seq (zero-or-more (any " \t"))
                                                      "#+end"
                                                      ,(match-string 4)
                                                      word-end
                                                      (zero-or-more any)))))
                   ;; We look further than LIMIT on purpose.
                   nil t)
              ;; We do have a matching #+end line.
              (setq beg-of-endline (match-beginning 0)
                    end-of-endline (match-end 0)
                    nl-before-endline (1- (match-beginning 0)))
              (setq block-end (match-beginning 0)) ; Include the final newline.
              (when quoting
                (org-remove-flyspell-overlays-in bol-after-beginline nl-before-endline)
                (remove-text-properties beg end-of-endline
                                        '(display t invisible t intangible t)))
              (add-text-properties
               beg end-of-endline '(font-lock-fontified t font-lock-multiline t))
              (org-remove-flyspell-overlays-in beg bol-after-beginline)
              (org-remove-flyspell-overlays-in nl-before-endline end-of-endline)
              (cond
               ((and lang (not (string= lang "")) org-src-fontify-natively)
                (org-src-font-lock-fontify-block lang block-start block-end)
                (add-text-properties bol-after-beginline block-end '(src-block t)))
               (quoting
                (add-text-properties
                 bol-after-beginline beg-of-endline
                 (list 'face
                       (list :inherit
                             (let ((face-name
                                    (intern (format "org-block-%s" lang))))
                               (append (and (facep face-name) (list face-name))
                                       '(org-block)))))))

               (el-patch-add
                 ((quark/org-fontify-custom-special-block-face block-type)
                  (add-face-text-property bol-after-beginline beg-of-endline
                   (quark/org-fontify-custom-special-block-face block-type)
                   t)))

               ((not org-fontify-quote-and-verse-blocks))
               ((string= block-type "quote")
                (add-face-text-property
                 bol-after-beginline beg-of-endline 'org-quote t))
               ((string= block-type "verse")
                (add-face-text-property
                 bol-after-beginline beg-of-endline 'org-verse t)))
              ;; Fontify the #+begin and #+end lines of the blocks
              (add-text-properties
               beg (if whole-blockline bol-after-beginline end-of-beginline)
               '(face org-block-begin-line))
              (unless (eq (char-after beg-of-endline) ?*)
                (add-text-properties
                 beg-of-endline
                 (if whole-blockline
                     (let ((beg-of-next-line (1+ end-of-endline)))
                       (min (point-max) beg-of-next-line))
                   (min (point-max) end-of-endline))
                 '(face org-block-end-line)))
              t))
           ((member dc1 '("+title:" "+author:" "+email:" "+date:"))
            (org-remove-flyspell-overlays-in
             (match-beginning 0)
             (if (equal "+title:" dc1) (match-end 2) (match-end 0)))
            (add-text-properties
             beg (match-end 3)
             (if (member (intern (substring dc1 1 -1)) org-hidden-keywords)
                 '(font-lock-fontified t invisible t)
               '(font-lock-fontified t face org-document-info-keyword)))
            (add-text-properties
             (match-beginning 6) (min (point-max) (1+ (match-end 6)))
             (if (string-equal dc1 "+title:")
                 '(font-lock-fontified t face org-document-title)
               '(font-lock-fontified t face org-document-info))))
           ((string-prefix-p "+caption" dc1)
            (org-remove-flyspell-overlays-in (match-end 2) (match-end 0))
            (remove-text-properties (match-beginning 0) (match-end 0)
                                    '(display t invisible t intangible t))
            ;; Handle short captions
            (save-excursion
              (beginning-of-line)
              (looking-at (rx (group (zero-or-more (any " \t"))
                                     "#+caption"
                                     (optional "[" (zero-or-more any) "]")
                                     ":")
                              (zero-or-more (any " \t")))))
            (add-text-properties (line-beginning-position) (match-end 1)
                                 '(font-lock-fontified t face org-meta-line))
            (add-text-properties (match-end 0) (line-end-position)
                                 '(font-lock-fontified t face org-block))
            t)
           ((member dc3 '(" " ""))
            ;; Just a comment, the plus was not there
            (org-remove-flyspell-overlays-in beg (match-end 0))
            (add-text-properties
             beg (match-end 0)
             '(font-lock-fontified t face font-lock-comment-face)))
           (t ;; Just any other in-buffer setting, but not indented
            (org-remove-flyspell-overlays-in (match-beginning 0) (match-end 0))
            (remove-text-properties (match-beginning 0) (match-end 0)
                                    '(display t invisible t intangible t))
            (add-text-properties beg (match-end 0)
                                 '(font-lock-fontified t face org-meta-line))
            t)))))))

Maths and LaTeX fragments

;; Better LaTeX previews (async)
(use-package xenops
  :hook
  (org-mode    . xenops-mode)
  (latex-mode  . xenops-mode)
  (xenops-mode . quark/xenops-apply-settings))

(defun quark/xenops-apply-settings ()
  "Configure xenops. For some reason I can't set them the usual way with `use-package'."
  (setq xenops-reveal-on-entry t)
  (setq xenops-math-image-scale-factor 1.2)
  ;; Workaround for https://github.com/dandavison/xenops/issues/59
  (defun xenops-math-reveal (element)
    "Remove image overlay for ELEMENT.
  If a prefix argument is in effect, also delete its cache file."
    (xenops-element-overlays-delete element)
    (if current-prefix-arg
        (delete-file (xenops-math-get-cache-file element)))
    (let ((element-type (plist-get element :type))
          (begin-content (plist-get element :begin-content))))
    (xenops-math-render-below-maybe element)))
;; Investigate xenops-font-family and xenops-font-height, particularly for latex

(use-package cdlatex
  :hook (org-mode . org-cdlatex-mode))

;; Default dvipng doesn't support tikz, but imagemagick is broken
;; Solution from github.com/enseishugi/org-mode-tikz-preview (modified for luatex)
(with-eval-after-load 'org
  (add-to-list 'org-preview-latex-process-alist
               '(graphicsmagick
                 :programs ("latex" "gm")
                 :description "pdf > png"
                 :message "you need to install the programs: latex and graphicsmagick."
                 :image-input-type "pdf"
                 :image-output-type "png"
                 :image-size-adjust (1.0 . 1.0)
                 :latex-compiler ("lualatex -interaction nonstopmode -output-directory %o %f")
                 :image-converter ("gm convert -density %D -trim -antialias %f -quality 100 %O")))
  (setq org-preview-latex-default-process 'graphicsmagick))

Export

LaTeX

(defvar quark/org-latex-classes-list
  '("quark-notes"
    "uonmathreport-colour"
    "uonmathreport22")
  "List of custom LaTeX classes.")

(defvar quark/org-latex-classes-sectioning
  '(("\\section{%s}" . "\\section*{%s}")
    ("\\subsection{%s}" . "\\subsection*{%s}")
    ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
  "Commands for exporting `org-mode' headings to LaTeX article classes.
Further subheadings become lists.")

(defvar quark/org-latex-classes-sectioning-book
  '(("\\chapter{%s}" . "\\chapter*{%s}")
    ("\\section{%s}" . "\\section*{%s}")
    ("\\subsection{%s}" . "\\subsection*{%s}")
    ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))
  "Commands for exporting `org-mode' headings to LaTeX book classes.
Further subheadings become lists.")

(defun quark/org-latex-classes-add-to-list ()
  "Enable custom LaTeX classes for `org-mode' LaTeX export."
  (dolist (class quark/org-latex-classes-list)
    (let ((documentclass (format "\\documentclass{%s}" class)))
      (add-to-list 'org-latex-classes
                   (cons class (cons documentclass
                                     quark/org-latex-classes-sectioning)))))
  (add-to-list 'org-latex-classes
               (append '("article-12pt" "\\documentclass[a4paper,12pt]{article}")
                       quark/org-latex-classes-sectioning))

  (add-to-list 'org-latex-classes
               (append '("quark-book" "\\documentclass{quark-book}")
                       quark/org-latex-classes-sectioning-book)))


(defun quark/org-latex-filter-ignoreheading (headline backend info)
  "Do not export headings tagged :ignoreheading:, but keep the contents."
  (when (and (org-export-derived-backend-p backend 'latex)
             (string-match "\\`.*ignoreheading.*\n" headline))
    (replace-match "" nil nil headline)))

(with-eval-after-load 'ox-latex
  (setq org-latex-compiler "lualatex")
  (setq org-latex-pdf-process '("latexmk -%latex -pvc -view=none %f"))
  (quark/org-latex-classes-add-to-list)
  (setq org-latex-default-class "quark-notes")
  (add-to-list 'org-export-filter-headline-functions 'quark/org-latex-filter-ignoreheading)
  (require 'oc-natbib)
  (require 'oc-biblatex)
  (setq org-cite-export-processors
        '((latex biblatex)
          ;(latex natbib "plain" "numeric")
          (t basic))))

(setq org-latex-packages-alist
      '(("" "tikz-cd" t)
        ("" "bm" t)
        ("" "mathrsfs" t)
        ("" "dsfont" t)
        ("" "quark-macros" t)
        ("" "quark-colours" t)))

Source blocks and babel

(with-eval-after-load 'org

  ;; List of languages org-babel can evaluate.
  (org-babel-do-load-languages
    'org-babel-load-languages
    '((emacs-lisp . t)
      (python . t)
      (shell . t)
      (latex . t)))

  (setq org-confirm-babel-evaluate nil)

  ;; Enable structure templates.
  (require 'org-tempo)

  (dolist (template
           '(("el"   . "src emacs-lisp")
             ("sh"   . "src sh")
             ("b"    . "src bash")
             ("z"    . "src zsh")
             ("py"   . "src python")
             ("hs"   . "src haskell")
             ("css"  . "src css")
             ("html" . "export html")
             ("def"  . "definition")
             ("rem"  . "remark")
             ("lem"  . "lemma")
             ("thm"  . "theorem")
             ("cor"  . "corollary")
             ("prf"  . "proof")
             ("com"  . "comment")
             ("eg"   . "eg")))
    (add-to-list 'org-structure-template-alist template))

  ;; Recognise extra languages
  (push '("conf-unix" . conf-unix) org-src-lang-modes)
  (push '("latex" . latex) org-src-lang-modes)

  (defun org-babel-sh-mode   () (sh-mode) (sh-set-shell "posix"))
  (defun org-babel-bash-mode () (sh-mode) (sh-set-shell "bash" ))
  (defun org-babel-zsh-mode  () (sh-mode) (sh-set-shell "zsh"  ))

  (push '("sh"   . org-babel-sh  ) org-src-lang-modes)
  (push '("bash" . org-babel-bash) org-src-lang-modes)
  (push '("zsh"  . org-babel-zsh ) org-src-lang-modes))

The package org-src-emph adds a :emph header argument which can be used to fix the syntax highlighting of shell source blocks containing noweb references, by setting :emph '(("<<" ">>")).

(use-package org-src-emph
  :straight (org-src-emph :host github :repo "TobiasZawada/org-src-emph"))

Org Roam

(defun quark/org-roam-make-basic-capture-template (KEY DESCRIPTION SUBDIRECTORY)
  "Take a KEY, DESCRIPTION and SUBDIRECTORY and return an Org Roam capture template."
  `(,KEY ,DESCRIPTION plain "%?"
        :if-new
        (file+head
         ,(format "%s/${slug}.org" SUBDIRECTORY)
         "#+TITLE: ${title}\n#+STARTUP: inlineimages\n\n")
        :immediate-finish t
        :unnarrowed t))

(defun quark/mapcar* (FUNCTION SEQUENCE)
  "Apply FUNCTION to each element of SEQUENCE, where each element is a list of arguments to pass."
  (mapcar (apply-partially 'apply FUNCTION) SEQUENCE))
(use-package org-roam
  :bind
  (("C-c n l" . org-roam-buffer-toggle)
   ("C-c n f" . org-roam-node-find)
   ("C-c n i" . org-roam-node-insert)
   ("C-c n c" . org-roam-capture))
  :custom
  (org-roam-directory (concat quark/work-dir "/roam-notes"))
  (org-roam-completion-everywhere t) ;; Use completion-at-point (C-M-i) to complete note names
  (org-roam-node-display-template
   (concat
    "${type:6} ${title:*} "  ;; Node type from org-roam-node-type below
    (propertize "${tags:10}" 'face 'org-tag)))
  (org-roam-capture-templates
   (append
     (quark/mapcar*
      'quark/org-roam-make-basic-capture-template
      '(("m" "by me"        "mine")
        ("n" "course notes" "notes")
        ("w" "wiki"         "wiki")
        ("i" "inbox"        "inbox")))))
  :config
  (org-roam-db-autosync-mode)
  ;; Differentiate between note types based on directory
  (cl-defmethod org-roam-node-type ((node org-roam-node))
    "Return the TYPE of a NODE based on its subdirectory.
From https://jethrokuan.github.io/org-roam-guide."
    (condition-case nil
        (file-name-nondirectory
         (directory-file-name
          (file-name-directory
           (file-relative-name
            (org-roam-node-file node) org-roam-directory))))
      (error ""))))
(use-package org-roam-ui
  :after org-roam)

Org-ref

(use-package pdf-tools)

(use-package ivy-bibtex
  :custom
  (bibtex-completion-bibliography (concat quark/work-dir "/bibliography.bib"))
  (bibtex-completion-library-path '("~/Zotero/storage/" "~/Documents/"))
  (bibtex-completion-pdf-field "file")
  (bibtex-completion-notes-path (concat quark/work-dir "/roam-notes/bib/"))
  (bibtex-completion-notes-template-multiple-files
   "#+TITLE: Notes on: ${author-abbrev} (${year}): ${title}\n\nSee [[cite:&${=key=}]]\n\n")
  (bibtex-completion-additional-search-fields '(keywords))
  (bibtex-completion-pdf-open-function))

(use-package org-ref
  :bind
  (:map org-mode-map
        ("C-c ]" . org-ref-insert-link)
        ("C-c [" . org-ref-insert-link-hydra/body))
  :config
  (require 'org-ref-refproc)
  (add-hook 'org-export-before-parsing-hook 'org-ref-refproc))

(require 'org-ref-ivy)
(setq
 org-ref-insert-link-function  'org-ref-insert-link-hydra/body
 org-ref-insert-cite-function  'org-ref-cite-insert-ivy
 org-ref-insert-label-function 'org-ref-insert-label-ivy)

(use-package org)
(use-package org-roam-bibtex
  :after '(org-roam org-ref)
  :hook (org-mode . org-roam-bibtex-mode)
  :custom
  (orb-roam-ref-format 'org-ref-v3))

LaTeΧ

(use-package tex :straight auctex)

;; Mostly just to enable system fonts.
(setq TeX-engine "lualatex")

Magit

(use-package magit)
  ;; Doesn't seem to work when deferred
  ;:commands (magit-status quark/magit-status quark/dotfiles-magit-status))

(use-package git-gutter
  :init (global-git-gutter-mode))

I backup all my dotfiles using a git bare repository. It is difficult for Emacs to tell automatically whether I want to use magit on my dotfiles repository or some other repository, as they can overlap. Following this Stackexchange answer, I set up alternative commands to launch Magit explicitly with the correct settings.

;; Add args when used for dotfiles or remove args otherwise.
(setq dotfiles-git-dir (concat "--git-dir=" (expand-file-name "~/.dotfiles-git")))
(setq dotfiles-work-tree (concat "--work-tree=" (expand-file-name "~")))

(defun quark/dotfiles-magit-status ()
  "Open magit to manage my dotfiles git bare repository."
  (interactive)
  (add-to-list 'magit-git-global-arguments dotfiles-git-dir)
  (add-to-list 'magit-git-global-arguments dotfiles-work-tree)
  (call-interactively 'magit-status))

(defun quark/magit-status ()
  "Replacement for `magit-status' for compatibility with quark/dotfiles-magit-status."
  (interactive)
  (setq magit-git-global-arguments (remove dotfiles-git-dir magit-git-global-arguments))
  (setq magit-git-global-arguments (remove dotfiles-work-tree magit-git-global-arguments))
  (call-interactively 'magit-status))

(general-def "C-x g" 'quark/magit-status)
(general-def magit-file-mode-map "C-x g" 'quark/magit-status)

(my-leader-def
  "g" '(:ignore t :which-key "git")
  "gd" '(quark/dotfiles-magit-status :which-key "dotfiles-magit-status")
  "gg" '(quark/magit-status          :which-key "magit-status"))

File management

Dired

(use-package dired
  :straight nil ;; Preinstalled - don't try to find on MELPA.
  :commands (dired dired-jump)
  :bind ("C-x C-j" . dired-jump)
  :custom (dired-listing-switches "-Agho --group-directories-first"))

;; Stop dired from making a new buffer for each directory.
(use-package dired-single
  :after dired
  :config
  (evil-collection-define-key 'normal 'dired-mode-map
    "h" 'dired-single-up-directory
    "l" 'dired-single-buffer))

(use-package all-the-icons-dired
  :hook (dired-mode . all-the-icons-dired-mode))

;; Font lock rules for dired.
(use-package diredfl
  :hook (dired-mode . diredfl-mode))

(use-package dired-hide-dotfiles
  :hook (dired-mode . dired-hide-dotfiles-mode)
  :config
  (evil-collection-define-key 'normal 'dired-mode-map
    "H" 'dired-hide-dotfiles-mode))

;; Collapse trivial file hierarchies.
(use-package dired-collapse
  :hook (dired-mode . dired-collapse-mode))

Projectile

(use-package projectile
  ;:pin melpa-stable
  :init (projectile-mode)
  :bind-keymap ("C-c p" . projectile-command-map))

(use-package counsel-projectile
  :after projectile
  :init (counsel-projectile-mode))

(my-leader-def
  "SPC" 'projectile-find-file
  "p" '(:ignore t :which-key "projects")
  "pp" 'projectile-switch-project)

Miscellaneous

;; More helpful help pages.
(use-package helpful
  :custom
  (counsel-describe-function-function #'helpful-callable)
  (counsel-describe-variable-function #'helpful-variable)
  :bind
  ([remap describe-comand] . helpful-command)
  ([remap describe-key] . helpful-key))

;; Manage .pacnew and .pacsave files on Arch Linux.
(use-package pacfiles-mode
  :commands pacfiles-start)

;; Serve local websites
(use-package simple-httpd)
(defun quark/display-startup-time ()
  "Print the time Emacs took to start."
  (message "Emacs loaded in %s with %d garbage collections."
           (format "%.2f seconds"
                   (float-time
                    (time-subtract after-init-time before-init-time)))
           gcs-done))
(add-hook 'emacs-startup-hook #'quark/display-startup-time)

Must return garbage collection back to a reasonable value after startup to prevent stuttering. The package gcmh also reduced garbage collection when not idling.

(setq gc-cons-threshold 2000000
      gc-cons-percentage 0.1)
(use-package gcmh
  :config (gcmh-mode 1))

About

My configuration for the Emacs text editor

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published