Emacs configuration for writing
Table of Contents
- 1. Emacs native configuration
- 2. Convenience functions
- 3. Packages
- 4. Appearance
(quail-set-keyboard-layout "jp106")
1. Emacs native configuration
1.1. UI
1.1.1. Disable unnecessary UIs
(tool-bar-mode -1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(setq inhibit-startup-message t)
1.1.2. fill-column
(setq-default fill-column 79)
(global-display-fill-column-indicator-mode 1)
1.1.3. Fringe
(set-fringe-mode 0)
1.1.4. Line numbering
(global-display-line-numbers-mode t)
(global-set-key (kbd "M-o l") 'display-line-numbers-mode)
(global-set-key (kbd "M-o L") 'global-display-line-numbers-mode)
1.1.5. Line wrapping
(global-visual-line-mode t)
1.1.6. Cursor
(global-hl-line-mode 1)
(blink-cursor-mode -1)
1.1.7. tab-width
(setq-default tab-width 2)
(setq-default indent-tabs-mode nil)
(setq-default c-basic-offset 2)
1.1.8. column-number
(setq column-number-indicator-zero-based nil)
(column-number-mode t)
1.2. Behaviour
1.2.1. savehist
(savehist-mode 1)
1.2.2. Backups and lockfiles
(setq backup-directory-alist '(("." . "~/.cache/emacs/")))
(setq backup-inhibited t)
(setq make-backup-files nil)
(setq auto-save-visited-interval 2)
(auto-save-visited-mode)
(setq create-lockfiles nil)
(setq kill-buffer-delete-auto-save-files t)
1.2.3. window
(setq window-min-height 5)
- Interactive toggle functions
(defun window-toggle-no-delete-other-windows () (interactive) (if (window-parameter (selected-window) 'no-delete-other-windows) (progn (set-window-parameter (selected-window) 'no-delete-other-windows nil) (message "Set `no-delete-other-windows' to nil")) (progn (set-window-parameter (selected-window) 'no-delete-other-windows t) (message "Set `no-delete-other-windows' to t")))) (defun window-toggle-no-other-windows () (interactive) (if (window-parameter (selected-window) 'no-other-windows) (progn (set-window-parameter (selected-window) 'no-other-windows nil) (message "Set `no-other-windows' to nil")) (progn (set-window-parameter (selected-window) 'no-other-windows t) (message "Set `no-other-windows' to t")))) (global-set-key (kbd "C-x w k") 'window-toggle-no-delete-other-windows) (global-set-key (kbd "C-x w o") 'window-toggle-no-other-windows)
1.2.4. Terminal mouse support
(unless (display-graphic-p)
(xterm-mouse-mode 1))
1.3. Personal setups
1.3.1. Registers
(set-register ?i (cons 'file (file-truename "~/giabao.xyz/org/notes/emacs-configuration-for-writing.org")))
(global-set-key (kbd "C-c r") 'jump-to-register)
1.4. dired
(require 'dired)
(setq dired-dwim-target t)
(defun my/dired-mode-hook ()
(display-line-numbers-mode -1)
(display-fill-column-indicator-mode -1)
(dired-hide-details-mode 1))
(add-hook 'dired-mode-hook #'my/dired-mode-hook)
(defun dired-recursive-toggle ()
"Toggle the `-R' switch of `ls'."
(interactive)
(if (eq (dired-switches-recursive-p dired-actual-switches)
nil)
(dired-sort-other "-Rl")
(dired-sort-other "-al")))
(define-key dired-mode-map (kbd "r") #'dired-recursive-toggle)
2. Convenience functions
2.1. zathura
(defvar zathura-found-service
"Stores found Zathura session PID.")
(defun zathura--check-file-opened (file)
(let* ((services (dbus-list-known-names :session))
(zathura-services (seq-filter
(lambda (s)
(string-prefix-p "org.pwmt.zathura.PID-" s))
services)))
(when zathura-services
(setq zathura-found-service nil)
(let ((zathura-services-matched
(mapcar
(lambda (service)
(if (equal file (dbus-get-property :session service
"/org/pwmt/zathura"
"org.pwmt.zathura"
"filename"))
(setq zathura-found-service service)))
zathura-services)))
(when zathura-found-service
(remove nil zathura-services-matched))))))
3. Packages
(require 'package)
(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("melpa" . "https://melpa.org/packages/")))
(package-initialize)
(unless package-archive-contents
(package-refresh-contents))
(require 'use-package)
(setq use-package-always-ensure t)
3.1. org-mode
(use-package org
:vc (:url "https://code.tecosaur.net/tec/org-mode.git"
:branch "dev"
:lisp-dir "lisp"))
3.1.1. General setup
(setq org-special-ctrl-a/e t)
(setq org-hide-leading-stars nil)
(setq org-startup-indented t)
(setq org-use-tag-inheritance nil)
(setq org-tags-sort-function 'org-string-collate-lessp)
(setq org-tags-column 0)
(setq org-use-property-inheritance t)
(setq org-enforce-todo-dependencies t)
(setq org-deadline-warning-days 0)
(setq org-property-format "%-11s %s")
(setq org-highlight-latex-and-related '(native))
(setq org-file-apps
'((auto-mode . emacs)
("\\.pdf\\'" . "zathura \"%s\"")))
(setq org-blank-before-new-entry
'((heading . nil)
(plain-list-item . nil)))
(setq org-list-allow-alphabetical 1)
3.1.2. org-id
(setq org-id-method 'ts
org-id-ts-format "%Y%m%dT%H%M%S"
org-id-link-to-org-use-id nil)
(defun my/org-heading-insert-id ()
(interactive)
(save-excursion
(org-back-to-heading)
(org-set-property "ID" (org-id-new))))
(define-key org-mode-map (kbd "M-o H") #'my/org-heading-insert-id)
;; (add-hook 'org-insert-heading-hook #'my/org-heading-insert-id)
(defun my/org-heading-kill-ring-save-id ()
(interactive)
(kill-new (org-entry-get (point) "ID" t)))
(define-key org-mode-map (kbd "M-o h") #'my/org-heading-kill-ring-save-id)
3.1.3. General org-mode keybinds
3.1.4. org-num
(require 'org-num)
(setq org-num-skip-tags '("unnumbered"))
(setq org-num-skip-unnumbered t)
(define-key org-mode-map (kbd "M-o n") #'org-num-mode)
(defun my/org-num-format (numbering)
(concat
"["
(apply 'concat
(cl-loop for n in numbering
for i from 1 to (length numbering)
if (cl-oddp i) collect (number-to-string n)
else collect (downcase (org-number-to-letters n))))
"] "))
(setq org-num-format-function #'my/org-num-format)
3.1.5. ol
(setq org-image-max-width 'fill-column)
(define-key org-mode-map (kbd "C-c l") #'org-store-link)
(define-key org-mode-map (kbd "M-o c") #'org-toggle-link-display)
3.1.6. org-fold
(define-key org-mode-map (kbd "M-o b") #'org-fold-hide-block-all)
(define-key org-mode-map (kbd "M-o d") #'org-fold-hide-drawer-all)
- Toggling headline content at current position
https://emacs.stackexchange.com/questions/26827/test-whether-org-mode-heading-or-list-is-folded
(defun my/org-headline-content-toggle () (interactive) (if (eq (and (or (org-at-heading-p) (org-at-item-p)) (invisible-p (point-at-eol))) nil) (org-fold-hide-entry) (org-fold-show-entry))) (define-key org-mode-map (kbd "M-s") #'my/org-headline-content-toggle)
3.1.7. org-babel
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(C . t)
(sqlite . t)
(shell . t)
(latex . t)
(matlab . t)
(lua . t)
(python . t)
(gnuplot . t)))
3.1.8. org-cite
(require 'oc)
(setq org-cite-global-bibliography
(mapcar 'file-truename
(directory-files-recursively "~/documents/bibliographies/" "\\.bib$")))
(defun org-cite-basic--get-offset (citekey)
(org-cite-basic--get-field 'offset citekey))
3.1.9. org-latex-preview
(setq org-latex-preview-process-default 'dvisvgm)
3.1.10. org-entities
(setq org-entities-user
'(("bigl" "\\bigl" t "" "" "" "◔")
("Bigl" "\\Bigl" t "" "" "" "◑")
("biggl" "\\biggl" t "" "" "" "◕")
("Biggl" "\\Biggl" t "" "" "" "●")
("bigr" "\\bigl" t "" "" "" "◔")
("Bigr" "\\Bigl" t "" "" "" "◑")
("biggr" "\\biggl" t "" "" "" "◕")
("Biggr" "\\Biggl" t "" "" "" "●")))
(setq org-pretty-entities-include-sub-superscripts nil)
(add-hook 'org-mode-hook #'org-toggle-pretty-entities)
(defun my/prettify-symbols-setup-org ()
(push '("[note:" . "[") prettify-symbols-alist)
(push '("eqref" . "⮴") prettify-symbols-alist)
(setq-local prettify-symbols-unprettify-at-point nil)
(prettify-symbols-mode))
(add-hook 'org-mode-hook #'my/prettify-symbols-setup-org)
(global-set-key (kbd "M-o p") #'prettify-symbols-mode)
3.1.11. hook
(defun my/org-mode-hook ()
(display-line-numbers-mode -1)
(org-num-mode 1))
(add-hook 'org-mode-hook #'my/org-mode-hook)
3.2. org-visual-outline
(use-package org-visual-outline
:vc (:url "https://github.com/legalnonsense/org-visual-outline.git"
:branch "master"
:rev :newest)
:defer t
:load-path (lambda () (expand-file-name "elpa/org-visual-outline" user-emacs-directory)))
(require 'org-visual-indent)
(defun org-visual-indent-apply ()
(interactive)
(progn
(face-spec-set 'org-visual-indent-blank-pipe-face
`((t ( :foreground ,(face-background 'default)
:background ,(face-background 'default)
:height .1))))
(setq org-visual-indent-color-indent
(cl-loop for x from 1 to 8
with color = nil
do (setq color (or (face-foreground
(intern
(concat "outline-"
(number-to-string x))))
(face-foreground 'org-level-1)))
collect `(,x ,(list
:background color
:foreground color
:height .1))))
(org-mode-restart)))
(add-hook 'org-mode-hook #'org-visual-indent-mode)
(require 'org-dynamic-bullets)
;; (setq org-dynamic-bullets-folded-body-text-bullet "*")
;; (setq org-dynamic-bullets-folded-no-body-text-bullet "*")
;; (setq org-dynamic-bullets-unfolded-body-text-bullet "*")
;; (setq org-dynamic-bullets-unfolded-no-body-text-bullet "*")
;; (setq org-dynamic-bullets-leaf-body-text-bullet "*")
;; (setq org-dynamic-bullets-leaf-no-body-text-bullet "*")
(setq org-dynamic-bullets-folded-body-text-bullet "▶"
org-dynamic-bullets-folded-no-body-text-bullet "▷"
org-dynamic-bullets-unfolded-body-text-bullet "▼"
org-dynamic-bullets-unfolded-no-body-text-bullet "▽"
org-dynamic-bullets-leaf-body-text-bullet "▬"
org-dynamic-bullets-leaf-no-body-text-bullet "▭")
(add-hook 'org-mode-hook #'org-dynamic-bullets-mode)
3.3. org-mem
(use-package inspector)
(use-package emacsql)
(use-package org-mem)
(setq org-mem-watch-dirs '("~/giabao.xyz/org/")
org-mem-do-sync-with-org-id t)
(org-mem-updater-mode)
(org-mem-roamy-db-mode)
3.4. org-node
(use-package org-node)
(org-node-cache-mode)
(setq org-node-backlink-drawer-formatter
#'org-node-backlink-format-as-bullet-with-time)
(global-set-key (kbd "M-n n") 'org-node-find)
(define-key org-mode-map (kbd "M-n i") #'org-node-insert-link)
(defun org-node-time-stamp (with-time inactive &optional time zone)
"Make a timestamp passing WITH-TIME, INACTIVE to `org-time-stamp-format'.
Pass TIME, ZONE to `format-time-string'."
(format-time-string (org-time-stamp-format with-time inactive)
time zone))
(setq org-node-property-crtime "CREATED")
(setq org-node-property-mtime "MODIFIED")
(add-hook 'org-node-modification-hook
#'org-node-update-mtime-property)
(defun org-node-backlink--fix-nearby-drawer (&optional remove)
"Update nearby backlinks drawer so it reflects current reality.
If REMOVE non-nil, remove it instead."
(if remove
(org-node--delete-drawer "BACKLINKS")
(let* ((id (org-entry-get nil "ID"))
(entry (org-mem-entry-by-id id))
(portal-ids (mapcar #'car
(emacsql (org-mem-roamy-db)
[:select node_id
:from tags
:where (in tag $v1)]
(vconcat org-excluded-tags))))
(all-origin-ids (when entry
(thread-last
(append (org-mem-id-links-to-entry entry)
(org-mem-roam-reflinks-to-entry entry))
(seq-keep #'org-mem-link-nearby-id)
(delete id)
(delete-dups)
;; Shouldn't be necessary if tables are correct, but
;; don't assume `org-mem-updater-mode' is flawless
(seq-filter #'org-mem-entry-by-id))))
(origin-ids (seq-difference all-origin-ids portal-ids))
(org-node-backlink--inhibit-flagging t))
(if (null origin-ids)
(org-node--delete-drawer "BACKLINKS")
(save-excursion
(save-restriction
(org-node-narrow-to-drawer-create
"BACKLINKS" org-node-backlink-drawer-positioner)
(let* ((col (current-indentation))
(lines (split-string (buffer-string) "\n" t))
(already-present-ids
(seq-keep #'org-node-backlink--extract-id lines))
(to-add (seq-difference origin-ids already-present-ids))
(to-remove (seq-difference already-present-ids origin-ids))
(to-reformat (seq-intersection already-present-ids origin-ids)))
;; Add new, remove stale, reformat the rest
(dolist (id to-remove)
(save-excursion
(search-forward id)
(delete-line)))
(dolist (id to-reformat)
(save-excursion
(search-forward id)
(back-to-indentation)
(let* ((line (buffer-substring-no-properties (point) (pos-eol)))
(reformatted (org-node-backlink--reformat-line line)))
(unless (equal line reformatted)
(atomic-change-group
(delete-region (point) (pos-eol))
(insert reformatted))))))
(dolist (id to-add)
(let ((title (org-mem-title-maybe (org-mem-entry-by-id id))))
(insert (funcall org-node-backlink-drawer-formatter id title))
(newline)
(indent-to col)))
;; Membership is correct, now re-sort
(let ((sorted-lines (sort (split-string (buffer-string) "\n" t)
org-node-backlink-drawer-sorter)))
(when org-node-backlink-drawer-sort-in-reverse
(setq sorted-lines (nreverse sorted-lines)))
(unless (equal lines sorted-lines)
(atomic-change-group
(delete-region (point-min) (point-max))
(insert (string-join sorted-lines "\n"))))))))))))
3.5. tex-mode
(setq tex-fontify-script nil
font-latex-fontify-script nil)
3.6. citar
(use-package citar)
(setq citar-bibliography org-cite-global-bibliography
org-cite-insert-processor 'citar
org-cite-follow-processor 'citar
org-cite-activate-processor 'citar
citar-library-paths-recursive t
citar-library-paths '("~/documents/books/"
"~/documents/articles/"
"~/documents/other/")
citar-notes-paths '("~/notes/bibliography/")
citar-file-variable "file")
(setq citar-file-open-functions
(list
(cons "html" #'citar-file-open-external)
(cons "pdf" #'citar-file-open-external)
(cons "epub" #'citar-file-open-external)
(cons "djvu" #'citar-file-open-external)
(cons t #'find-file)))
(add-hook 'LaTeX-mode-hook #'citar-capf-setup)
(add-hook 'org-mode-hook #'citar-capf-setup)
(global-set-key (kbd "M-n o") #'citar-open)
(global-set-key (kbd "M-n c") #'citar-insert-citation)
(setq citar-templates
'((main . "${date year issued}, ${author editor:%sn}, ${title}")
(suffix . " (${=type=}) [${=key= id}] ${tags keywords keywords:*}")
(preview . "${author editor:%etal} (${year issued date}) ${title}, ${journal journaltitle publisher container-title collection-title}.")
(note . "Notes on ${author editor:%etal}, ${title}")))
3.6.1. Integrate with zathura
Integrate citar links with zathura.
See 2.1.
Open specified page in "p. page" of citar links in zathura.
(defun zathura-citar-parse-offset-string (offset-string)
"Return a list of cons cells, where each cons cell is (PDF_PAGE . REAL_PAGE)."
(let (parsed-labels)
(dolist (pair-str (split-string offset-string "," t))
(when (string-match "\\([0-9]+\\):\\([0-9]+\\)" pair-str)
(let* ((pdf-page (string-to-number (match-string 1 pair-str)))
(real-page (string-to-number (match-string 2 pair-str))))
(push (cons pdf-page real-page) parsed-labels))))
(sort parsed-labels (lambda (a b) (< (cdr a) (cdr b))))))
(defun zathura-citar-get-pdf-pagenumber-from-real-pagenumber (offset-string real-page-num)
"Return pdf page number."
(if (not (equal offset-string nil))
(let* ((parsed-labels (zathura-citar-parse-offset-string offset-string))
(best-match nil))
(cl-block nil
(dolist (segment parsed-labels)
(let ((real-start (cdr segment)))
(when (<= real-start real-page-num)
(setq best-match segment))
(when (and (> real-start real-page-num) best-match)
(cl-return nil)))))
(if best-match
(let* ((pdf-start (car best-match))
(real-start (cdr best-match))
(offset (- real-page-num real-start)))
(+ pdf-start offset))
(progn
(message "No mapping found for real page %d" real-page-num)
nil)))
(message "Offset string is nil.")
nil))
(defun zathura-org-follow-cite-link-with-page-number-at-point ()
(interactive)
(thing-at-point-looking-at "@\\([a-z0-9-.]*\\) p\\. \\([0-9]*\\)\\(-[0-9]*\\)?")
(let* ((citekey (org-match-string-no-properties 1))
(page (string-to-number (org-match-string-no-properties 2)))
(file (car (gethash citekey (citar-get-files citekey))))
(offset (org-cite-basic--get-field 'offset citekey))
(pagenumber (zathura-citar-get-pdf-pagenumber-from-real-pagenumber offset page))
(zathura-selected-pid (car (zathura--check-file-opened file))))
(if zathura-selected-pid
(dbus-call-method :session
zathura-selected-pid
"/org/pwmt/zathura"
"org.pwmt.zathura"
"GotoPage"
:uint32 (1- pagenumber))
(start-process "zathura-viewer" nil "zathura" file "-P" (number-to-string pagenumber)))))
(define-key org-mode-map (kbd "M-o f") #'zathura-org-follow-cite-link-with-page-number-at-point)
3.7. AUCTeX and CDLaTeX
(use-package auctex)
(use-package cdlatex)
(add-hook 'org-mode-hook #'org-cdlatex-mode)
(setq cdlatex-insert-auto-labels-in-env-templates nil)
3.8. braket
(use-package bratex
:vc (:url "https://github.com/sbrisard/bratex.git"
:branch "master"))
(add-hook 'org-shiftup-final-hook #'bratex-cycle-size)
(add-hook 'org-shiftdown-final-hook #'bratex-cycle-size-reverse)
(add-hook 'org-shiftright-final-hook #'bratex-cycle-bracket)
(add-hook 'org-shiftleft-final-hook #'bratex-cycle-bracket-reverse)
3.9. yasnippet
(use-package yasnippet)
(yas-global-mode 1)
3.10. transpose-frame
(use-package transpose-frame)
(global-set-key (kbd "C-x w t") 'transpose-frame)
3.11. clipetty
(use-package clipetty)
(global-clipetty-mode 1)
3.12. ebib
(use-package ebib
:bind ("C-c e" . ebib))
(setq ebib-bibtex-dialect 'biblatex)
3.13. vertico
(use-package vertico)
(setq vertico-scroll-margin 0
vertico-count 20
vertico-resize t
vertico-cycle t)
(vertico-mode 1)
(vertico-mouse-mode 1)
3.14. orderless
(use-package orderless)
(setq completion-styles '(orderless basic)
completion-category-defaults nil
completion-category-overrides '((file (styles partial-completion))))
3.15. marginalia
(use-package marginalia)
(define-key minibuffer-local-map (kbd "M-A") #'marginalia-cycle)
(marginalia-mode 1)
3.16. consult
(use-package consult
:hook (completion-list-mode . consult-preview-at-point-mode)
:init
(advice-add #'register-preview :override #'consult-register-window)
(setq register-preview-delay 0.5)
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
:config
(consult-customize
consult-theme :preview-key '(:debounce 0.2 any)
consult-ripgrep consult-git-grep consult-grep consult-man
consult-bookmark consult-recent-file consult-xref
consult--source-bookmark consult--source-file-register
consult--source-recent-file consult--source-project-recent-file
;; :preview-key "M-."
:preview-key '(:debounce 0.4 any)))
3.16.1. consult-org
(define-key org-mode-map (kbd "M-o o") #'consult-org-heading)
3.17. embark
(use-package embark)
3.17.1. embark-consult
(use-package embark-consult
:bind
(("C-." . embark-act)
("C-;" . embark-dwim)
("C-h B" . embark-bindings)))
4. Appearance
4.1. Font
(set-face-attribute 'default nil :family "Aporetic Sans Mono" :height 120)
4.2. Theme
4.2.1. doom-themes
(use-package doom-themes)
(load-theme 'doom-one-light t)
4.2.2. org-visual-indent hook
(org-visual-indent-apply)
(add-hook 'load-theme-hook #'org-visual-indent-apply)
4.2.3. leuvenizer
Everyone loves the theme Leuven because of its colorful org headings. Why not make it every where :)))
- colormixer
https://emacs.stackexchange.com/a/69051/44287
(defun my/color-blend-names (color-a color-b factor) "Tint between COLOR-A and COLOR-B by FACTOR in (0..1). Use for mixing face colors." (let ((value-a (color-values color-a)) (value-b (color-values color-b)) (ifactor (- 1.0 factor))) (apply 'format (cons "#%02x%02x%02x" (mapcar (lambda (n) ;; Shift by -8 to map the value returned by `color values': ;; 0..65535 to 0..255 for `#RRGGBB` string formatting. (ash (truncate (+ (* (nth n value-a) ifactor) (* (nth n value-b) factor))) -8)) (number-sequence 0 2)))))) - org headings
(defun leuvenize-org-levels (to-level factor) "Set org-level-* according to loaded theme's colors" (cl-loop for x from 1 to to-level do (set-face-attribute (intern (concat "org-level-" (number-to-string x))) nil :weight 'regular :box nil :overline t :underline nil :background (my/color-blend-names (face-foreground (intern (concat "org-level-" (number-to-string x))) nil t) (face-background 'default nil t) factor)))) (defun unleuvenize-org-levels () "Set org-level-* according to loaded theme's colors" (cl-loop for x from 1 to 8 do (set-face-attribute (intern (concat "org-level-" (number-to-string x))) nil :box 'unspecified :overline 'unspecified :underline 'unspecified :background 'unspecified))) - leuvenize!
(setq leuvenize-org-factor 0.75) (defun leuvenize () (interactive) (progn (setq org-fontify-whole-heading-line t) (leuvenize-org-levels 3 leuvenize-org-factor))) (leuvenize) (add-hook 'load-theme-hook #'leuvenize)