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.
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
.
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)
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)
(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))
(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.")
(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))
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)
(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)
(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))
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)
(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)
;; 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))
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"))
; 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)
(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))
(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)))
(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))
https://zzamboni.org/post/beautifying-org-mode-in-emacs/
(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)))
(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)))
(setq org-directory "~/Org"
org-default-notes-file (concat org-directory "/notes.org"))
(general-def :prefix "C-c"
"l" 'org-store-link
"a" 'org-agenda
"c" 'org-capture)
(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))
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)))))))
;; 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))
(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)))
(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"))
(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)
(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))
(use-package tex :straight auctex)
;; Mostly just to enable system fonts.
(setq TeX-engine "lualatex")
(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"))
(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))
(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)
;; 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))