130 KiB
Doom Emacs Configuration
- Intro
- Rudimentary configuration
- Package loading
- Package configuration
- Language configuration
Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do. — Donald Knuth
Intro
Customising an editor can be very rewarding … until you have to leave it. For years I have been looking for ways to avoid this pain. Then I discovered vim-anywhere, and found that it had an Emacs companion, emacs-anywhere. To me, this looked most attractive.
Separately, online I have seen the following statement enough times I think it's a catchphrase
Redditor1: I just discovered this thing, isn't it cool. Redditor2: Oh, there's an emacs mode for that.
I tried out the spacemacs
distribution a bit, but it wasn't quite to my liking.
Then I heard about doom emacs
and thought I may as well give that a try.
TLDR; it's great.
Now I've discovered the wonders of literate programming, and am becoming more settled by the day. This is my config.
Notes for the unwary adventurer
If you are so bold as to wish to steal bits of my config (or if I upgrade and wonder why things aren't working), here's a list of sections which rely on externally setup (i.e. outside of this config).
- libvterm
- I rely on my distro-provided
libvterm.so
, vterm sets a compile flag. If this causes issues, just delete the(setq term-module-cmake-args ...)
bit. - dictionary
- I've downloaded a custom SCOWL dictionary, which I use in ispell.
If this causes issues, just delete the
(setq ispell-dictionary ...)
bit. - uni-units file
- I've got a file in
~/.org/.uni-units
which I use inorg-capture
If this causes issues, just remove the reference to that file in Capture and instances ofunit-prompt
used in(doct ...)
Extra Requirements
The lovely doom doctor
is good at diagnosing most missing things, but here are a
few extras.
-
The Delta binary. It's packaged for some distributions but I installed it with
cargo install git-delta
- A LaTeX Compiler is required for the mathematics rendering performed in Org, and by CalcTeX.
-
I use the Overpass font as a go-to sans serif. It's used as my
doom-variable-pitch-font
and in the graph generated by Roam. I have chosen it because it possesses a few characteristics I consider desirable, namely:- A clean, and legible style. Highway-style fonts tend to be designed to be clear at a glance, and work well with a thicker weight, and this is inspired by Highway Gothic.
- It's slightly quirky. Look at the diagonal cut on stems for example. Helvetica is a masterful design, but I like a bit more pizazz now and then.
- A few LSP servers. Take a look at init.el to see which modules have the
+lsp
flag. -
The
cargo-script
rust crate is required for evaluation of rust blocks by babel. As described in the README for ob-rust. Likedelta
, this can just be installed using cargo.cargo install cargo-script
Current Issues
Magit push in daemon
Quite often trying to push to a remote in the emacs daemon produces as error like this:
128 git … push -v origin refs/heads/master\:refs/heads/master
Pushing to git@github.com:tecosaur/emacs-config.git
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
CalcTeX brings up compilation buffer
With my Calc hook, the first call of M-x calc
brings up a compilation buffer
from CalcTeX. I'm guessing this is from the compilation of the preamble / .fmt
file.
I can't get low effort pretty gnuplots
I'd like to have some pretty defaults, and some nice presets like :preset radar
.
Perhaps an defadvice!
is in org-plot/gnuplot
's near future?
Wish list
- Nice default font, line thicknesses etc.
- Colours based on current doom theme
- a
:preset
key, which takes a string/variable/function. A bit likeTeX-fold-math-spec-alist
.
Rudimentary configuration
Make this file run (slightly) faster with lexical binding (see this blog post for more info).
;;; config.el -*- lexical-binding: t; -*-
Personal Information
It's useful to have some basic personal information
(setq user-full-name "tecosaur"
user-mail-address "tecosaur@gmail.com")
Apparently this is used by GPG
, and all sorts of other things.
Better defaults
Simple settings
Browsing the web and seeing angrybacon/dotemacs and comparing with the values
shown by SPC h v
and selecting what I thought looks good, I've ended up adding the following:
(setq-default
delete-by-moving-to-trash t ; Delete files to trash
tab-width 4 ; Set width for tabs
uniquify-buffer-name-style 'forward ; Uniquify buffer names
window-combination-resize t ; take new window space from all other windows (not just current)
x-stretch-cursor t) ; Stretch cursor to the glyph width
(setq undo-limit 80000000 ; Raise undo-limit to 80Mb
evil-want-fine-undo t ; By default while in insert all changes are one big blob. Be more granular
auto-save-default t ; Nobody likes to loose work, I certainly don't
inhibit-compacting-font-caches t) ; When there are lots of glyphs, keep them in memory
(delete-selection-mode 1) ; Replace selection when inserting text
(display-time-mode 1) ; Enable time in the mode-line
(display-battery-mode 1) ; On laptops it's nice to know how much power you have
(global-subword-mode 1) ; Iterate through CamelCase words
Fullscreen
I also like the idea of fullscreen-ing when opened by emacs
or the .desktop
file.
(if (eq initial-window-system 'x) ; if started by emacs command or desktop file
(toggle-frame-maximized)
(toggle-frame-fullscreen))
Auto-customisations
By default changes made via a customisation interface are added to init.el
.
I prefer the idea of using a separate file for this. We just need to change a
setting, and load it if it exists.
(setq-default custom-file (expand-file-name ".custom.el" doom-private-dir))
(when (file-exists-p custom-file)
(load custom-file))
Windows
I find it rather handy to be asked which buffer I want to see after splitting the window. Let's make that happen. First, we'll enter the new window
(setq evil-vsplit-window-right t
evil-split-window-below t)
Then, we'll pull up ivy
(defadvice! prompt-for-buffer (&rest _)
:after '(evil-window-split evil-window-vsplit)
(+ivy/switch-buffer))
Oh, and previews are nice
(setq +ivy-buffer-preview t)
Buffer defaults
I'd much rather have my new buffers in org-mode
than fundamental-mode
, hence
;; (setq-default major-mode 'org-mode)
For some reason this + the mixed pitch hook causes issues with hydra and so I'll
just need to resort to SPC b o
for now.
Doom configuration
Visual Settings
Font Face
'Fira Code' is nice, and 'Overpass' makes for a nice sans companion. We just need to fiddle with the font sizes a tad so that they visually match.
(setq doom-font (font-spec :family "Fira Code" :size 22)
doom-big-font (font-spec :family "Fira Code" :size 36)
doom-variable-pitch-font (font-spec :family "Overpass" :size 24))
Theme
doom-one
is nice and all, but I find the vibrant
variant nicer.
(setq doom-theme 'doom-vibrant)
However, by default red
text is used in the modeline
, so let's make that orange
so I don't feel like something's gone wrong when editing files.
(custom-set-faces!
'(doom-modeline-buffer-modified :foreground "orange"))
Miscellaneous
Relative line numbers are fantastic for knowing how far away line numbers are,
then ESC 12 <UP>
gets you exactly where you think.
(setq display-line-numbers-type 'relative)
I'd like some slightly nicer default buffer names
(setq doom-fallback-buffer-name "► Doom"
+doom-dashboard-name "► Doom")
There's a bug with the modeline in insert mode for org documents (issue), so
(custom-set-faces! '(doom-modeline-evil-insert-state :weight bold :foreground "#339CDB"))
Some helper macros
There are a few handy macros added by doom, namely
load!
for loading external.el
files relative to this oneuse-package
for configuring packagesadd-load-path!
for adding directories to theload-path
whereemacs
looks when you load packages withrequire
oruse-package
map
for binding new keys
To find more,
Other things
Editor interaction
Mouse buttons
(map! :n [mouse-8] #'better-jumper-jump-backward
:n [mouse-9] #'better-jumper-jump-forward)
Window title
I'd like to have just the buffer name, then if applicable the project folder
(setq frame-title-format
'(""
(:eval
(if (s-contains-p org-roam-directory (or buffer-file-name ""))
(replace-regexp-in-string ".*/[0-9]+-" "🢔 " buffer-file-name)
"%b"))
(:eval
(let ((project-name (projectile-project-name)))
(unless (string= "-" project-name)
(format (if (buffer-modified-p) " ◉ %s" " ● %s") project-name))))))
Splash screen
Emacs can render an image as the splash screen, and @MarioRicalde came up with a cracker! He's also provided me with a nice emacs-style E, which is good for smaller windows. @MarioRicalde you have my sincere thanks, you're great!
By incrementally stripping away the outer layers of the logo one can obtain quite a nice resizing effect.
(defvar fancy-splash-image-template
(expand-file-name "misc/splash-images/blackhole-lines-template.svg" doom-private-dir)
"Default template svg used for the splash image, with substitutions from ")
(defvar fancy-splash-image-nil
(expand-file-name "misc/splash-images/transparent-pixel.png" doom-private-dir)
"An image to use at minimum size, usually a transparent pixel")
(setq fancy-splash-sizes
`((:height 500 :min-height 50 :padding (0 . 4) :template ,(expand-file-name "misc/splash-images/blackhole-lines-0.svg" doom-private-dir))
(:height 440 :min-height 42 :padding (1 . 4) :template ,(expand-file-name "misc/splash-images/blackhole-lines-0.svg" doom-private-dir))
(:height 400 :min-height 38 :padding (1 . 4) :template ,(expand-file-name "misc/splash-images/blackhole-lines-1.svg" doom-private-dir))
(:height 350 :min-height 36 :padding (1 . 3) :template ,(expand-file-name "misc/splash-images/blackhole-lines-2.svg" doom-private-dir))
(:height 300 :min-height 34 :padding (1 . 3) :template ,(expand-file-name "misc/splash-images/blackhole-lines-3.svg" doom-private-dir))
(:height 250 :min-height 32 :padding (1 . 2) :template ,(expand-file-name "misc/splash-images/blackhole-lines-4.svg" doom-private-dir))
(:height 200 :min-height 30 :padding (1 . 2) :template ,(expand-file-name "misc/splash-images/blackhole-lines-5.svg" doom-private-dir))
(:height 100 :min-height 24 :padding (1 . 2) :template ,(expand-file-name "misc/splash-images/emacs-e-template.svg" doom-private-dir))
(:height 0 :min-height 0 :padding (0 . 0) :file ,fancy-splash-image-nil)))
(defvar fancy-splash-sizes
`((:height 500 :min-height 50 :padding (0 . 2))
(:height 440 :min-height 42 :padding (1 . 4))
(:height 330 :min-height 35 :padding (1 . 3))
(:height 200 :min-height 30 :padding (1 . 2))
(:height 0 :min-height 0 :padding (0 . 0) :file ,fancy-splash-image-nil))
"list of plists with the following properties
:height the height of the image
:min-height minimum `frame-height' for image
:padding `+doom-dashboard-banner-padding' to apply
:template non-default template file
:file file to use instead of template")
(defvar fancy-splash-template-colours
'(("$colour1" . keywords) ("$colour2" . type) ("$colour3" . base5) ("$colour4" . base8))
"list of colour-replacement alists of the form (\"$placeholder\" . 'theme-colour) which applied the template")
(unless (file-exists-p (expand-file-name "theme-splashes" doom-cache-dir))
(make-directory (expand-file-name "theme-splashes" doom-cache-dir) t))
(defun fancy-splash-filename (theme-name height)
(expand-file-name (concat (file-name-as-directory "theme-splashes")
(symbol-name doom-theme)
"-" (number-to-string height) ".svg")
doom-cache-dir))
(defun fancy-splash-clear-cache ()
"Delete all cached fancy splash images"
(interactive)
(delete-directory (expand-file-name "theme-splashes" doom-cache-dir) t)
(message "Cache cleared!"))
(defun fancy-splash-generate-image (template height)
"Read TEMPLATE and create an image if HEIGHT with colour substitutions as ;described by `fancy-splash-template-colours' for the current theme"
(with-temp-buffer
(insert-file-contents template)
(re-search-forward "$height" nil t)
(replace-match (number-to-string height) nil nil)
(dolist (substitution fancy-splash-template-colours)
(beginning-of-buffer)
(while (re-search-forward (car substitution) nil t)
(replace-match (doom-color (cdr substitution)) nil nil)))
(write-region nil nil
(fancy-splash-filename (symbol-name doom-theme) height) nil nil)))
(defun fancy-splash-generate-images ()
"Perform `fancy-splash-generate-image' in bulk"
(dolist (size fancy-splash-sizes)
(unless (plist-get size :file)
(fancy-splash-generate-image (or (plist-get size :file)
(plist-get size :template)
fancy-splash-image-template)
(plist-get size :height)))))
(defun ensure-theme-splash-images-exist (&optional height)
(unless (file-exists-p (fancy-splash-filename
(symbol-name doom-theme)
(or height
(plist-get (car fancy-splash-sizes) :height))))
(fancy-splash-generate-images)))
(defun get-appropriate-splash ()
(let ((height (frame-height)))
(cl-some (lambda (size) (when (>= height (plist-get size :min-height)) size))
fancy-splash-sizes)))
(setq fancy-splash-last-size nil)
(setq fancy-splash-last-theme nil)
(defun set-appropriate-splash (&optional frame)
(let ((appropriate-image (get-appropriate-splash)))
(unless (and (equal appropriate-image fancy-splash-last-size)
(equal doom-theme fancy-splash-last-theme)))
(unless (plist-get appropriate-image :file)
(ensure-theme-splash-images-exist (plist-get appropriate-image :height)))
(setq fancy-splash-image
(or (plist-get appropriate-image :file)
(fancy-splash-filename (symbol-name doom-theme) (plist-get appropriate-image :height))))
(setq +doom-dashboard-banner-padding (plist-get appropriate-image :padding))
(setq fancy-splash-last-size appropriate-image)
(setq fancy-splash-last-theme doom-theme)
(+doom-dashboard-reload)))
(add-hook 'window-size-change-functions #'set-appropriate-splash)
(add-hook 'doom-load-theme-hook #'set-appropriate-splash)
Systemd daemon
For running a systemd service for a Emacs server I have the following
[Unit]
Description=Emacs server daemon
Documentation=info:emacs man:emacs(1) https://gnu.org/software/emacs/
[Service]
Type=forking
ExecStart=/usr/bin/emacs --daemon
ExecStop=/usr/bin/emacsclient --eval "(progn (setq kill-emacs-hook nil) (kill emacs))"
Environment=SSH_AUTH_SOCK=%t/keyring/ssh
Restart=on-failure
[Install]
WantedBy=default.target
which is then enabled by
systemctl --user enable emacs.service
Package loading
This file shouldn't be byte compiled.
;; -*- no-byte-compile: t; -*-
Loading instructions
This is where you install packages, by declaring them with the package!
macro, then running doom refresh
on the command line. You'll need to
restart Emacs for your changes to take effect! Or at least, run M-x doom/reload
.
WARNING: Don't disable core packages listed in ~/.emacs.d/core/packages.el
.
Doom requires these, and disabling them may have terrible side effects.
Packages in MELPA/ELPA/emacsmirror
To install some-package
from MELPA, ELPA or emacsmirror:
(package! some-package)
Packages from git repositories
To install a package directly from a particular repo, you'll need to specify
a :recipe
. You'll find documentation on what :recipe
accepts here:
(package! another-package
:recipe (:host github :repo "username/repo"))
If the package you are trying to install does not contain a PACKAGENAME.el
file, or is located in a subdirectory of the repo, you'll need to specify
:files
in the :recipe
:
(package! this-package
:recipe (:host github :repo "username/repo"
:files ("some-file.el" "src/lisp/*.el")))
Disabling built-in packages
If you'd like to disable a package included with Doom, for whatever reason,
you can do so here with the :disable
property:
(package! builtin-package :disable t)
You can override the recipe of a built in package without having to specify
all the properties for :recipe
. These will inherit the rest of its recipe
from Doom or MELPA/ELPA/Emacsmirror:
(package! builtin-package :recipe (:nonrecursive t))
(package! builtin-package-2 :recipe (:repo "myfork/package"))
Specify a :branch
to install a package from a particular branch or tag.
This is required for some packages whose default branch isn't 'master' (which
our package manager can't deal with; see raxod502/straight.el#279)
(package! builtin-package :recipe (:branch "develop"))
Updating all pins in file
(defun +doom/update-all-pinned-package-form ()
"Call `doom/update-pinned-package-form' on every package! statement in the buffer"
(interactive)
(beginning-of-buffer)
(let ((progress 0) (package "") (total (how-many "package!")) (updated '()))
(while (search-forward "package!" nil t)
(setq progress (1+ progress))
(forward-char) ;; move cursor to package name "package! |name"
(setq package (current-word))
(message (format "Re-pinning package: %s/%s (%s)" progress total package))
(backward-char)
(evil-scroll-line-to-center (line-number-at-pos))
(redisplay)
(if (s-contains-p "Updated" (condition-case nil
(doom/update-pinned-package-form)
(user-error "")))
(add-to-list 'updated package))
(search-forward "package!" nil t)) ;; because of cursor-moving done
(message (format "%s packages updated (%s)" (length updated) (s-join ", " updated)))
updated))
General packages
Prettification
prettify-mode
is nice and all, but adding substitutions is a little verbose.
This helps with that.
(package! prettify-utils ; simplify messing with prettify-mode
:recipe (:host github :repo "Ilazki/prettify-utils.el") :pin "8b783d316c")
Window management
(package! rotate :pin "091b5ac4fc")
Fun
Sometimes one just wants a little fun. XKCD comics are fun.
(package! xkcd :pin "66e928706f")
Every so often, you want everyone else to know that you're typing, or just to amuse oneself. Introducing: typewriter sounds!
(package! selectric-mode :pin "bb9e66678f")
Hey, let's get the weather in here while we're at it.
(package! wttrin :pin "df5427ce2a")
Why not flash words on the screen. Why not — hey, it could be fun.
(package! spray :pin "00638bc916")
With all our fancy Emacs themes, my terminal is missing out!
(package! theme-magic :pin "844c4311bd")
What's even the point of using Emacs unless you're constantly telling everyone about it?
(package! elcord :pin "94b0afb9ba")
Improving features
Flyspell-lazy
To alleviate some issues with flyspell
(package! flyspell-lazy :pin "3ebf68cc9e")
CalcTeX
This is a nice extension to calc
(package! calctex :recipe (:host github :repo "johnbcoughlin/calctex"
:files ("*.el")) :pin "542b628eb4")
ESS
View data frames better with
(package! ess-view :pin "d4e5a340b7")
Magit Delta
Delta is a git diff syntax highlighter written in rust. The author also wrote a
package to hook this into the magit diff view. This requires the delta
binary.
;; (package! magit-delta :recipe (:host github :repo "dandavison/magit-delta") :pin "0c7d8b2359")
Language packages
Systemd
For editing systemd unit files
(package! systemd :pin "51c148e09a")
Org Mode
Improve agenda/capture
The agenda is nice, but a souped up version is nicer.
(package! org-super-agenda :pin "dd0d104c26")
Similarly doct
(Declarative Org Capture Templates) seems to be a nicer way to
set up org-capture.
(package! doct
:recipe (:host github :repo "progfolio/doct")
:pin "1bcec209e1")
Visuals
Org tables aren't the prettiest thing to look at. This package is supposed to redraw them in the buffer with box-drawing characters. Sounds like an improvement to me! Just need to get it working…
(package! org-pretty-table-mode
:recipe (:host github :repo "Fuco1/org-pretty-table") :pin "88380f865a")
For automatically toggling LaTeX fragment previews there's this nice package
(package! org-fragtog :pin "8eca8084cc")
org-superstar-mode
is great. While we're at it we may as well make tags prettier as well :)
(package! org-pretty-tags :pin "40fd72f3e7")
Extra functionality
Because of the lovely variety in markdown implementations there isn't actually
such a thing a standard table spec … or standard anything really. Because
org-md
is a goody-two-shoes, it just uses HTML for all these non-standardised
elements (a lot of them). So ox-gfm
is handy for exporting markdown with all the
features that GitHub has. Initialised in /tec/emacs-config/src/commit/620f2c57d0830ef7c8582cc593834c8668ec19d5/Exporting%20to%20GFM.
(package! ox-gfm :pin "99f93011b0")
Now and then citations need to happen
(package! org-ref :pin "4ce8064437")
Came across this and … it's cool
(package! org-graph-view :recipe (:host github :repo "alphapapa/org-graph-view") :pin "13314338d7")
I need this in my life. It take a URL to a recipe from a common site, and inserts an org-ified version at point. Isn't that just great.
(package! org-chef :pin "1dd73fd3db")
Package configuration
Abbrev mode
Thanks to use a single abbrev-table for multiple modes? - Emacs Stack Exchange I have the following.
(use-package abbrev
:init
(setq-default abbrev-mode t)
;; a hook funtion that sets the abbrev-table to org-mode-abbrev-table
;; whenever the major mode is a text mode
(defun tec/set-text-mode-abbrev-table ()
(if (derived-mode-p 'text-mode)
(setq local-abbrev-table org-mode-abbrev-table)))
:commands abbrev-mode
:hook
(abbrev-mode . tec/set-text-mode-abbrev-table)
:config
(setq abbrev-file-name (expand-file-name "abbrev.el" doom-private-dir))
(setq save-abbrevs 'silently))
Calc
Radians are just better
(setq calc-angle-mode 'rad ;; radians are rad
calc-algebraic-mode t ;; allows '2*x instead of 'x<RET>2*
calc-symbolic-mode t) ;; keeps stuff like √2 irrational for as long as possible
(after! calctex
(setq calctex-format-latex-header (concat calctex-format-latex-header
"\n\\usepackage{arevmath}")))
(add-hook 'calc-mode-hook #'calctex-mode)
Centaur Tabs
We want to make the tabs a nice, comfy size (36
), with icons. The modifier
marker is nice, but the particular default Unicode one causes a lag spike, so
let's just switch to an o
, which still looks decent but doesn't cause any
issues.
A 'active-bar' is nice, so let's have one of those. If we have it under
needs us to
turn on x-underline-at-decent
though. For some reason this didn't seem to work
inside the (after! ... )
block ¯\_(ツ)_/¯.
Then let's change the font to a sans serif, but the default one doesn't fit too
well somehow, so let's switch to 'P22 Underground Book'; it looks much nicer.
(after! centaur-tabs
(centaur-tabs-mode -1)
(setq centaur-tabs-height 36
centaur-tabs-set-icons t
centaur-tabs-modified-marker "o"
centaur-tabs-close-button "×"
centaur-tabs-set-bar 'above)
centaur-tabs-gray-out-icons 'buffer
(centaur-tabs-change-fonts "P22 Underground Book" 160))
;; (setq x-underline-at-descent-line t)
Company
It's nice to have completions almost all the time, in my opinion. Key strokes are just waiting to be saved!
(after! company
(setq company-idle-delay 0.5
company-minimum-prefix-length 2)
(setq company-show-numbers t)
(add-hook 'evil-normal-state-entry-hook #'company-abort)) ;; make aborting less annoying.
Now, the improvements from precident
are mostly from remembering history, so
let's improve that memory.
(setq-default history-length 1000)
(setq-default prescient-history-length 1000)
Plain Text
ispell
is nice, let's have it in text
, markdown
, and GFM
.
(set-company-backend! '(text-mode
markdown-mode
gfm-mode)
'(:seperate company-ispell
company-files
company-yasnippet))
We then configure the dictionary we're using in ispell.
ESS
company-dabbrev-code
is nice. Let's have it.
(set-company-backend! 'ess-r-mode '(company-R-args company-R-objects company-dabbrev-code :separate))
Elcord
(setq elcord-use-major-mode-as-main-icon t)
Emacs Anywhere configuration
It's nice to recognise GitHub (so we can use GFM
), and other apps which we know
take markdown
(defun markdown-window-p (window-title)
"Judges from WINDOW-TITLE whether the current window likes markdown"
(string-match-p (rx (or "Stack Exchange" "Stack Overflow"
"Pull Request" "Issue" "Discord"))
window-title))
When the window opens, we generally want text so let's use a nice sans serif font,
a position the window below and to the left. Oh, and don't forget about checking
for GFM
, otherwise let's just use markdown
.
(define-minor-mode emacs-anywhere-mode
"To tweak the current buffer for some emacs-anywhere considerations"
:init-value nil
:keymap (list
;; Finish edit, but be smart in org mode
(cons (kbd "C-c C-c") (lambda! (if (and (eq major-mode 'org-mode)
(org-in-src-block-p))
(org-ctrl-c-ctrl-c)
(delete-frame))))
;; Abort edit. emacs-anywhere saves the current edit for next time.
(cons (kbd "C-c C-k") (lambda! (setq ea-on nil)
(delete-frame))))
(when emacs-anywhere-mode
;; line breaking
(turn-off-auto-fill)
(visual-line-mode t)
;; DEL/C-SPC to clear (first keystroke only)
(set-transient-map (let ((keymap (make-sparse-keymap)))
(define-key keymap (kbd "DEL") (lambda! (delete-region (point-min) (point-max))))
(define-key keymap (kbd "C-SPC") (lambda! (delete-region (point-min) (point-max))))
keymap))
;; disable tabs
(when (bound-and-true-p centaur-tabs-mode)
(centaur-tabs-local-mode t))))
(defun ea-popup-handler (app-name window-title x y w h)
(interactive)
(set-frame-size (selected-frame) 80 12)
;; position the frame near the mouse
(let* ((mousepos (split-string (shell-command-to-string "xdotool getmouselocation | sed -E \"s/ screen:0 window:[^ ]*|x:|y://g\"")))
(mouse-x (- (string-to-number (nth 0 mousepos)) 100))
(mouse-y (- (string-to-number (nth 1 mousepos)) 50)))
(set-frame-position (selected-frame) mouse-x mouse-y))
(set-frame-name (concat "Quick Edit ∷ " ea-app-name " — "
(truncate-string-to-width
(string-trim
(string-trim-right window-title
(format "-[A-Za-z0-9 ]*%s" ea-app-name))
"[\s-]+" "[\s-]+")
45 nil nil "…")))
(message "window-title: %s" window-title)
;; set major mode
(cond
((markdown-window-p window-title) (gfm-mode))
(t (org-mode)) ; default major mode
)
(when-let ((selection (gui-get-selection 'PRIMARY)))
(insert selection)
;; I'll be honest with myself, I /need/ spellcheck
(flyspell-buffer))
(evil-insert-state) ; start in insert
(emacs-anywhere-mode 1))
(add-hook 'ea-popup-hook 'ea-popup-handler)
Eros-eval
This makes the result of evals with gr
and gR
just slightly prettier. Every bit
counts right?
(setq eros-eval-result-prefix "⟹ ")
EVIL
I don't use evil-escape-mode
, so I may as well turn it off, I've heard it
contributes a typing delay. I'm not sure it's much, but it is an extra
pre-command-hook
that I don't benefit from, so…
(after! evil (evil-escape-mode nil))
Flyspell
At one point, typing became noticeably laggy, Profiling revealed
flyspell-post-command-hook
was responsible for 47% of CPU cycles by itself!
So I'm going to make use of flyspell-lazy
(after! flyspell (require 'flyspell-lazy) (flyspell-lazy-mode 1))
Ispell
Let's get a nice big dictionary from SCOWL Custom List/Dictionary Creator with the following configuration
- size
- 80 (huge)
- spellings
- British(-ise) and Australian
- spelling variants level
- 0
- diacritics
- keep
- extra lists
- hacker, roman numerals
(setq ispell-dictionary "en_GBs_au_SCOWL_80_0_k_hr")
Oh, and by the way, if company-ispell-dictionary
is nil
, then
ispell-complete-word-dict
is used instead, which once again when nil
is
ispell-alternate-dictionary
, which at the moment maps to a plaintext version of
the above.
It seems reasonable to want to keep an eye on my personal dict, let's have it nearby (also means that if I change the 'main' dictionary I keep my addition).
(setq ispell-personal-dictionary (expand-file-name ".hunspell_personal" doom-private-dir))
Magit
Magit is pretty nice by default. The diffs don't get any syntax-highlighting-love though which is a bit sad. Thankfully dandavison/magit-delta exists, which we can put to use.
;; (after! magit
;; (magit-delta-mode +1))
Unfortunately this seems to mess things up, which is something I'll want to look into later.
Org Chef
Loading after org seems a bit premature. Let's just load it when we try to use it, either by command or in a capture template.
(use-package! org-chef
:commands (org-chef-insert-recipe org-chef-get-recipe-from-url))
Projectile
Looking at documentation via SPC h f
and SPC h v
and looking at the source can
add package src directories to projectile. This isn't desirable in my opinion.
(setq projectile-ignored-projects '("~/" "/tmp" "~/.emacs.d/.local/straight/repos/"))
(defun projectile-ignored-project-function (filepath)
"Return t if FILEPATH is within any of `projectile-ignored-projects'"
(or (mapcar (lambda (p) (s-starts-with-p p filepath)) projectile-ignored-projects)))
Smart Parentheses
(sp-local-pair
'(org-mode)
"<<" ">>"
:actions '(insert))
Spray
Let's make this suit me slightly better.
(setq spray-wpm 500
spray-height 700)
Theme magic
Let's automatically update terminals on theme change
(add-hook 'doom-load-theme-hook 'theme-magic-from-emacs)
Tramp
Let's try to make tramp handle prompts better
(after! tramp
(setenv "SHELL" "/bin/bash")
(setq tramp-shell-prompt-pattern "\\(?:^\\|
\\)[^]#$%>\n]*#?[]#$%>] *\\(\\[[0-9;]*[a-zA-Z] *\\)*")) ;; defult +
Troubleshooting
In case the remote shell is misbehaving, here are some things to try
Zsh
There are some escape code you don't want, let's make it behave more considerately.
if [[ "$TERM" == "dumb" ]]; then
unset zle_bracketed_paste
unset zle
PS1='$ '
return
fi
Treemacs
Quite often there are superfluous files I'm not that interested in. There's no good reason for them to take up space. Let's add a mechanism to ignore them.
(after! treemacs
(defvar treemacs-file-ignore-extensions '()
"File extension which `treemacs-ignore-filter' will ensure are ignored")
(defvar treemacs-file-ignore-globs '()
"Globs which will are transformed to `treemacs-file-ignore-regexps' which `treemacs-ignore-filter' will ensure are ignored")
(defvar treemacs-file-ignore-regexps '()
"RegExps to be tested to ignore files, generated from `treeemacs-file-ignore-globs'")
(defun treemacs-file-ignore-generate-regexps ()
"Generate `treemacs-file-ignore-regexps' from `treemacs-file-ignore-globs'"
(setq treemacs-file-ignore-regexps (mapcar 'dired-glob-regexp treemacs-file-ignore-globs)))
(if (equal treemacs-file-ignore-globs '()) nil (treemacs-file-ignore-generate-regexps))
(defun treemacs-ignore-filter (file full-path)
"Ignore files specified by `treemacs-file-ignore-extensions', and `treemacs-file-ignore-regexps'"
(or (member (file-name-extension file) treemacs-file-ignore-extensions)
(let ((ignore-file nil))
(dolist (regexp treemacs-file-ignore-regexps ignore-file)
(setq ignore-file (or ignore-file (if (string-match-p regexp full-path) t nil)))))))
(add-to-list 'treemacs-ignored-file-predicates #'treemacs-ignore-filter))
Now, we just identify the files in question.
(setq treemacs-file-ignore-extensions '(;; LaTeX
"aux"
"ptc"
"fdb_latexmk"
"fls"
"synctex.gz"
"toc"
;; LaTeX - glossary
"glg"
"glo"
"gls"
"glsdefs"
"ist"
"acn"
"acr"
"alg"
;; LaTeX - pgfplots
"mw"
;; LaTeX - pdfx
"pdfa.xmpi"
))
(setq treemacs-file-ignore-globs '(;; LaTeX
"*/_minted-*"
;; AucTeX
"*/.auctex-auto"
"*/_region_.log"
"*/_region_.tex"))
VTerm
On my system, I want to use the vterm
lib in /usr/bin/libvterm.so
. This is not
default behaviour. Without this I see the following error message.
make[2]: *** No rule to make target 'libvterm-prefix/src/libvterm/.libs/libvterm.a', needed by '../vterm-module.so'. Stop.
make[1]: *** [CMakeFiles/Makefile2:106: CMakeFiles/vterm-module.dir/all] Error 2
make: *** [Makefile:84: all] Error 2
I asked about this in a GitHub Issue, and the fix is to use the cmake
flag
-DUSE_SYSTEM_LIBVTERM=yes
. This can be set for when Doom recompiles vterm
on
updating, which saves me manually compiling with the flag.
(setq vterm-module-cmake-args "-DUSE_SYSTEM_LIBVTERM=yes")
I also use a 'fancy powerline setup' with Powerlevel10k, so I need to use a patched font for my terminal. Unfortunately I haven't quite figured out how to do this yet.
Which-key
Let's make this popup a bit faster
(setq which-key-idle-delay 0.5) ;; I need the help, I really do
I also think that having evil-
appear in so many popups is a bit too verbose, let's change that, and do a few other similar tweaks while we're at it.
(setq which-key-allow-multiple-replacements t)
(after! which-key
(pushnew!
which-key-replacement-alist
'(("" . "\\`+?evil[-:]?\\(?:a-\\)?\\(.*\\)") . (nil . "◂\\1"))
'(("\\`g s" . "\\`evilem--?motion-\\(.*\\)") . (nil . "◃\\1"))
))
Wttrin
Set the default city. It's initially Taipei
but I find the IP-locating that's
done perfectly acceptable, so let's make that happen.
(setq wttrin-default-cities '(""))
Language configuration
File Templates
For some file types, we overwrite defaults in the snippets directory, others need to have a template assigned.
(set-file-template! "\\.tex$" :trigger "__" :mode 'latex-mode)
Plaintext
It's nice to see ansi colour codes displayed
(after! text-mode
(add-hook! 'text-mode-hook
;; Apply ANSI color codes
(with-silent-modifications
(ansi-color-apply-on-region (point-min) (point-max)))))
Org Mode
System config
Org mode isn't recognised as it's own mime type by default, but that can easily
be changed with the following file. For system-wide changes try
~/usr/share/mime/packages/org.xml
.
<?xml version="1.0" encoding="utf-8"?>
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
<mime-type type="text/org">
<comment>Emacs Org-mode File</comment>
<glob pattern="*.org"/>
<alias type="text/org"/>
</mime-type>
</mime-info>
What's nice is that Papirus now has an icon for text/org
.
One simply needs to refresh their mime database
update-mime-database ~/.local/share/mime
Then set Emacs as the default editor
xdg-mime default emacs.desktop text/org
Behaviour
Tweaking defaults
(setq org-directory "~/.org" ; let's put files here
org-use-property-inheritance t ; it's convenient to have properties inherited
org-log-done 'time ; having the time a item is done sounds convininet
org-list-allow-alphabetical t ; have a. A. a) A) list bullets
org-export-in-background t ; run export processes in external emacs process
org-catch-invisible-edits 'smart) ; try not to accidently do weird stuff in invisible regions
I also like the :comments
header-argument, so let's make that a default.
(setq org-babel-default-header-args '((:session . "none")
(:results . "replace")
(:exports . "code")
(:cache . "no")
(:noweb . "no")
(:hlines . "no")
(:tangle . "no")
(:comments . "link")))
By default, visual-line-mode
is turned on
, and auto-fill-mode
off
by a hook.
However this messes with tables in Org-mode, and other plaintext files (e.g.
markdown, \LaTeX) so I'll turn it off for this, and manually enable it for more
specific modes as desired.
(remove-hook 'text-mode-hook #'visual-line-mode)
(add-hook 'text-mode-hook #'auto-fill-mode)
Extra functionality
Let's also make creating an org buffer just that little bit easier.
(evil-define-command evil-buffer-org-new (count file)
"Creates a new ORG buffer replacing the current window, optionally
editing a certain FILE"
:repeat nil
(interactive "P<f>")
(if file
(evil-edit file)
(let ((buffer (generate-new-buffer "*new org*")))
(set-window-buffer nil buffer)
(with-current-buffer buffer
(org-mode)))))
(map! :leader
(:prefix "b"
:desc "New empty ORG buffer" "o" #'evil-buffer-org-new))
I think it makes sense to have list bullets change with depth
(setq org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+")))
Occasionally I want to cite something.
(use-package! org-ref
:after org
:config
(setq org-ref-completion-library 'org-ref-ivy-cite))
It's also nice to be able to use cdlatex
.
(after! org (add-hook 'org-mode-hook 'turn-on-org-cdlatex))
At some point in the future it could be good to investigate splitting org blocks. Likewise this looks good for symbols.
My spelling is atrocious, so let's get flycheck going.
(after! org (add-hook 'org-mode-hook 'turn-on-flyspell))
Now, by default, LSPs don't really function at all in src
blocks.
(cl-defmacro lsp-org-babel-enable (lang)
"Support LANG in org source code block."
(setq centaur-lsp 'lsp-mode)
(cl-check-type lang stringp)
(let* ((edit-pre (intern (format "org-babel-edit-prep:%s" lang)))
(intern-pre (intern (format "lsp--%s" (symbol-name edit-pre)))))
`(progn
(defun ,intern-pre (info)
(let ((file-name (->> info caddr (alist-get :file))))
(unless file-name
(setq file-name (make-temp-file "babel-lsp-")))
(setq buffer-file-name file-name)
(lsp-deferred)))
(put ',intern-pre 'function-documentation
(format "Enable lsp-mode in the buffer of org source block (%s)."
(upcase ,lang)))
(if (fboundp ',edit-pre)
(advice-add ',edit-pre :after ',intern-pre)
(progn
(defun ,edit-pre (info)
(,intern-pre info))
(put ',edit-pre 'function-documentation
(format "Prepare local buffer environment for org source block (%s)."
(upcase ,lang))))))))
(defvar org-babel-lang-list
'("go" "python" "ipython" "bash" "sh"))
(dolist (lang org-babel-lang-list)
(eval `(lsp-org-babel-enable ,lang)))
Super agenda
(use-package! org-super-agenda
:commands (org-super-agenda-mode))
(after! org-agenda
(org-super-agenda-mode))
(setq org-agenda-skip-scheduled-if-done t
org-agenda-skip-deadline-if-done t
org-agenda-include-deadlines t
org-agenda-block-separator nil
org-agenda-tags-column 100 ;; from testing this seems to be a good value
org-agenda-compact-blocks t)
(setq org-agenda-custom-commands
'(("o" "Overview"
((agenda "" ((org-agenda-span 'day)
(org-super-agenda-groups
'((:name "Today"
:time-grid t
:date today
:todo "TODAY"
:scheduled today
:order 1)))))
(alltodo "" ((org-agenda-overriding-header "")
(org-super-agenda-groups
'((:name "Next to do"
:todo "NEXT"
:order 1)
(:name "Important"
:tag "Important"
:priority "A"
:order 6)
(:name "Due Today"
:deadline today
:order 2)
(:name "Due Soon"
:deadline future
:order 8)
(:name "Overdue"
:deadline past
:face error
:order 7)
(:name "Assignments"
:tag "Assignment"
:order 10)
(:name "Issues"
:tag "Issue"
:order 12)
(:name "Emacs"
:tag "Emacs"
:order 13)
(:name "Projects"
:tag "Project"
:order 14)
(:name "Research"
:tag "Research"
:order 15)
(:name "To read"
:tag "Read"
:order 30)
(:name "Waiting"
:todo "WAITING"
:order 20)
(:name "University"
:tag "uni"
:order 32)
(:name "Trivial"
:priority<= "E"
:tag ("Trivial" "Unimportant")
:todo ("SOMEDAY" )
:order 90)
(:discard (:tag ("Chore" "Routine" "Daily")))))))))))
Capture
Let's setup some org-capture templates
(use-package! doct
:commands (doct))
(after! org-capture
<<prettify-capture>>
(setq +org-capture-uni-units (split-string (f-read-text "~/.org/.uni-units")))
(setq +org-capture-recipies "~/Desktop/TEC/Organisation/recipies.org")
(defun +doct-icon-declaration-to-icon (declaration)
"Convert :icon declaration to icon"
(let ((name (pop declaration))
(set (intern (concat "all-the-icons-" (plist-get declaration :set))))
(face (intern (concat "all-the-icons-" (plist-get declaration :color))))
(v-adjust (or (plist-get declaration :v-adjust) 0.01)))
(apply set `(,name :face ,face :v-adjust ,v-adjust))))
(defun +doct-iconify-capture-templates (groups)
"Add declaration's :icon to each template group in GROUPS."
(let ((templates (doct-flatten-lists-in groups)))
(setq doct-templates (mapcar (lambda (template)
(when-let* ((props (nthcdr (if (= (length template) 4) 2 5) template))
(spec (plist-get (plist-get props :doct) :icon)))
(setf (nth 1 template) (concat (+doct-icon-declaration-to-icon spec)
"\t"
(nth 1 template))))
template)
templates))))
(setq doct-after-conversion-functions '(+doct-iconify-capture-templates))
(add-transient-hook! 'org-capture-select-template
(setq org-capture-templates
(doct `(("Personal todo" :keys "t"
:icon ("checklist" :set "octicon" :color "green")
:file +org-capture-todo-file
:prepend t
:headline "Inbox"
:type entry
:template ("* TODO %?"
"%i %a")
)
("Personal note" :keys "n"
:icon ("sticky-note-o" :set "faicon" :color "green")
:file +org-capture-todo-file
:prepend t
:headline "Inbox"
:type entry
:template ("* %?"
"%i %a")
)
("University" :keys "u"
:icon ("graduation-cap" :set "faicon" :color "purple")
:file +org-capture-todo-file
:headline "University"
:unit-prompt ,(format "%%^{Unit|%s}" (string-join +org-capture-uni-units "|"))
:prepend t
:type entry
:children (("Test" :keys "t"
:icon ("timer" :set "material" :color "red")
:template ("* TODO [#C] %{unit-prompt} %? :uni:tests:"
"SCHEDULED: %^{Test date:}T"
"%i %a"))
("Assignment" :keys "a"
:icon ("library_books" :set "material" :color "orange")
:template ("* TODO [#B] %{unit-prompt} %? :uni:assignments:"
"DEADLINE: %^{Due date:}T"
"%i %a"))
("Lecture" :keys "l"
:icon ("keynote" :set "fileicon" :color "orange")
:template ("* TODO [#C] %{unit-prompt} %? :uni:lecture:"
"%i %a"))
("Miscellaneous task" :keys "u"
:icon ("list" :set "faicon" :color "yellow")
:template ("* TODO [#D] %{unit-prompt} %? :uni:"
"%i %a"))))
("Email" :keys "e"
:icon ("envelope" :set "faicon" :color "blue")
:file +org-capture-todo-file
:prepend t
:headline "Inbox"
:type entry
:template ("* TODO %^{type|reply to|contact} %\\3 %? :email:"
"Send an email %^{urgancy|soon|ASAP|anon|at some point|eventually} to %^{recipiant}"
"about %^{topic}"
"%U %i %a"))
("Interesting" :keys "i"
:icon ("eye" :set "faicon" :color "lcyan")
:file +org-capture-todo-file
:prepend t
:headline "Interesting"
:type entry
:template ("* [ ] %{desc}%? :%{i-type}:"
"%i %a")
:children (("Webpage" :keys "w"
:icon ("globe" :set "faicon" :color "green")
:desc "%(org-cliplink-capture) "
:i-type "read:web"
)
("Article" :keys "a"
:icon ("file-text" :set "octicon" :color "yellow")
:desc ""
:i-type "read:reaserch"
)
("\tRecipie" :keys "r"
:icon ("spoon" :set "faicon" :color "dorange")
:file +org-capture-recipies
:headline "Unsorted"
:template "%(org-chef-get-recipe-from-url)"
)
("Information" :keys "i"
:icon ("info-circle" :set "faicon" :color "blue")
:desc ""
:i-type "read:info"
)
("Idea" :keys "I"
:icon ("bubble_chart" :set "material" :color "silver")
:desc ""
:i-type "idea"
)))
("Tasks" :keys "k"
:icon ("inbox" :set "octicon" :color "yellow")
:file +org-capture-todo-file
:prepend t
:headline "Tasks"
:type entry
:template ("* TODO %? %^G%{extra}"
"%i %a")
:children (("General Task" :keys "k"
:icon ("inbox" :set "octicon" :color "yellow")
:extra ""
)
("Task with deadline" :keys "d"
:icon ("timer" :set "material" :color "orange" :v-adjust -0.1)
:extra "\nDEADLINE: %^{Deadline:}t"
)
("Scheduled Task" :keys "s"
:icon ("calendar" :set "octicon" :color "orange")
:extra "\nSCHEDULED: %^{Start time:}t"
)
))
("Project" :keys "p"
:icon ("repo" :set "octicon" :color "silver")
:prepend t
:type entry
:headline "Inbox"
:template ("* %{time-or-todo} %?"
"%i"
"%a")
:file ""
:custom (:time-or-todo "")
:children (("Project-local todo" :keys "t"
:icon ("checklist" :set "octicon" :color "green")
:time-or-todo "TODO"
:file +org-capture-project-todo-file)
("Project-local note" :keys "n"
:icon ("sticky-note" :set "faicon" :color "yellow")
:time-or-todo "%U"
:file +org-capture-project-notes-file)
("Project-local changelog" :keys "c"
:icon ("list" :set "faicon" :color "blue")
:time-or-todo "%U"
:heading "Unreleased"
:file +org-capture-project-changelog-file))
)
("\tCentralised project templates"
:keys "o"
:type entry
:prepend t
:template ("* %{time-or-todo} %?"
"%i"
"%a")
:children (("Project todo"
:keys "t"
:prepend nil
:time-or-todo "TODO"
:heading "Tasks"
:file +org-capture-central-project-todo-file)
("Project note"
:keys "n"
:time-or-todo "%U"
:heading "Notes"
:file +org-capture-central-project-notes-file)
("Project changelog"
:keys "c"
:time-or-todo "%U"
:heading "Unreleased"
:file +org-capture-central-project-changelog-file))
))))))
It would also be nice to improve how the capture dialogue looks
(defun org-capture-select-template-prettier (&optional keys)
"Select a capture template, in a prettier way than default
Lisp programs can force the template by setting KEYS to a string."
(let ((org-capture-templates
(or (org-contextualize-keys
(org-capture-upgrade-templates org-capture-templates)
org-capture-templates-contexts)
'(("t" "Task" entry (file+headline "" "Tasks")
"* TODO %?\n %u\n %a")))))
(if keys
(or (assoc keys org-capture-templates)
(error "No capture template referred to by \"%s\" keys" keys))
(org-mks org-capture-templates
"Select a capture template\n━━━━━━━━━━━━━━━━━━━━━━━━━"
"Template key: "
`(("q" ,(concat (all-the-icons-octicon "stop" :face 'all-the-icons-red :v-adjust 0.01) "\tAbort")))))))
(advice-add 'org-capture-select-template :override #'org-capture-select-template-prettier)
(defun org-mks-pretty (table title &optional prompt specials)
"Select a member of an alist with multiple keys. Prettified.
TABLE is the alist which should contain entries where the car is a string.
There should be two types of entries.
1. prefix descriptions like (\"a\" \"Description\")
This indicates that `a' is a prefix key for multi-letter selection, and
that there are entries following with keys like \"ab\", \"ax\"…
2. Select-able members must have more than two elements, with the first
being the string of keys that lead to selecting it, and the second a
short description string of the item.
The command will then make a temporary buffer listing all entries
that can be selected with a single key, and all the single key
prefixes. When you press the key for a single-letter entry, it is selected.
When you press a prefix key, the commands (and maybe further prefixes)
under this key will be shown and offered for selection.
TITLE will be placed over the selection in the temporary buffer,
PROMPT will be used when prompting for a key. SPECIALS is an
alist with (\"key\" \"description\") entries. When one of these
is selected, only the bare key is returned."
(save-window-excursion
(let ((inhibit-quit t)
(buffer (org-switch-to-buffer-other-window "*Org Select*"))
(prompt (or prompt "Select: "))
case-fold-search
current)
(unwind-protect
(catch 'exit
(while t
(setq-local evil-normal-state-cursor (list nil))
(erase-buffer)
(insert title "\n\n")
(let ((des-keys nil)
(allowed-keys '("\C-g"))
(tab-alternatives '("\s" "\t" "\r"))
(cursor-type nil))
;; Populate allowed keys and descriptions keys
;; available with CURRENT selector.
(let ((re (format "\\`%s\\(.\\)\\'"
(if current (regexp-quote current) "")))
(prefix (if current (concat current " ") "")))
(dolist (entry table)
(pcase entry
;; Description.
(`(,(and key (pred (string-match re))) ,desc)
(let ((k (match-string 1 key)))
(push k des-keys)
;; Keys ending in tab, space or RET are equivalent.
(if (member k tab-alternatives)
(push "\t" allowed-keys)
(push k allowed-keys))
(insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) (propertize "›" 'face 'font-lock-comment-face) " " desc "…" "\n")))
;; Usable entry.
(`(,(and key (pred (string-match re))) ,desc . ,_)
(let ((k (match-string 1 key)))
(insert (propertize prefix 'face 'font-lock-comment-face) (propertize k 'face 'bold) " " desc "\n")
(push k allowed-keys)))
(_ nil))))
;; Insert special entries, if any.
(when specials
(insert "─────────────────────────\n")
(pcase-dolist (`(,key ,description) specials)
(insert (format "%s %s\n" (propertize key 'face '(bold all-the-icons-red)) description))
(push key allowed-keys)))
;; Display UI and let user select an entry or
;; a sub-level prefix.
(goto-char (point-min))
(unless (pos-visible-in-window-p (point-max))
(org-fit-window-to-buffer))
(let ((pressed (org--mks-read-key allowed-keys prompt)))
(setq current (concat current pressed))
(cond
((equal pressed "\C-g") (user-error "Abort"))
;; Selection is a prefix: open a new menu.
((member pressed des-keys))
;; Selection matches an association: return it.
((let ((entry (assoc current table)))
(and entry (throw 'exit entry))))
;; Selection matches a special entry: return the
;; selection prefix.
((assoc current specials) (throw 'exit current))
(t (error "No entry available")))))))
(when buffer (kill-buffer buffer))))))
(advice-add 'org-mks :override #'org-mks-pretty)
The org-capture bin is rather nice, but I'd be nicer with a smaller frame, and no modeline.
(setf (alist-get 'height +org-capture-frame-parameters) 15)
;; (alist-get 'name +org-capture-frame-parameters) "❖ Capture") ;; ATM hardcoded in other places, so changing breaks stuff
(setq +org-capture-fn
(lambda ()
(interactive)
(set-window-parameter nil 'mode-line-format 'none)
(org-capture)))
Roam
Basic settings
I'll just set this to be within Organisation
folder for now, in the future it
could be worth seeing if I could hook this up to a Nextcloud instance.
(setq org-roam-directory "~/Desktop/TEC/Organisation/Roam")
Registering roam protocol
The recommended method of registering a protocal is by registering a desktop application, which seems reasonable.
[Desktop Entry]
Name=Org-Protocol
Exec=emacsclient %u
Icon=emacs-icon
Type=Application
Terminal=false
MimeType=x-scheme-handler/org-protocol
To associate org-protocol://
links with the desktop file,
xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
Graph Behaviour
By default, clicking on an org-protocol://
link messes with the svg
view. To fix
this we can use an iframe
, however that requires shifting to an html
file.
Hence, we need to do a bit of overriding.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Roam Graph</title>
<meta name="viewport" content="width=device-width">
<style type="text/css">
svg {
position: relative;
top: 50vh;
left: 50vw;
transform: translate(-50%, -50%);
}
a > polygon {
transition-duration: 200ms;
transition-property: fill;
}
a > polyline {
transition-duration: 400ms;
transition-property: stroke;
}
a:hover > polygon {
fill: #d4d4d4;
}
a:hover > polyline {
stroke: #888;
}
</style>
<script type="text/javascript">
function create_iframe (url) {
i = document.createElement('iframe');
i.setAttribute('src', url);
i.style.setProperty('display', 'none');
document.body.append(i);
}
function listen_on_all_a () {
document.querySelectorAll("svg a").forEach(elem => {
elem.addEventListener('click', (e) => {
e.preventDefault();
create_iframe(elem.href.baseVal);
});
});
}
</script>
</head>
<body onload="listen_on_all_a()">
%s
</body>
</html>
(after! org-roam
(setq +org-roam-graph--html-template (replace-regexp-in-string "%\\([^s]\\)" "%%\\1" (f-read-text (concat doom-private-dir "misc/org-roam-template.html"))))
(defadvice! +org-roam-graph--build-html (&optional node-query)
"Generate a graph showing the relations between nodes in NODE-QUERY. HTML style."
:override #'org-roam-graph--build
(unless org-roam-graph-executable
(user-error "Can't find %s executable. Please check if it is in your path"
org-roam-graph-executable))
(let* ((node-query (or node-query
`[:select [file titles]
:from titles
,@(org-roam-graph--expand-matcher 'file t)]))
(graph (org-roam-graph--dot node-query))
(temp-dot (make-temp-file "graph." nil ".dot" graph))
(temp-graph (make-temp-file "graph." nil ".svg"))
(temp-html (make-temp-file "graph." nil ".html")))
(call-process org-roam-graph-executable nil 0 nil
temp-dot "-Tsvg" "-o" temp-graph)
(sleep-for 0.1)
(write-region (format +org-roam-graph--html-template (f-read-text temp-graph)) nil temp-html)
temp-html)))
Modeline file name
All those numbers! It's messy. Let's adjust this in a similar way that I have in theWindow title.
(defadvice! doom-modeline--reformat-roam (orig-fun)
:around #'doom-modeline-buffer-file-name
(message "Reformat?")
(message (buffer-file-name))
(if (s-contains-p org-roam-directory (or buffer-file-name ""))
(replace-regexp-in-string
"\\(?:^\\|.*/\\)\\([0-9]\\{4\\}\\)\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)[0-9]*-"
"🢔(\\1-\\2-\\3) "
(funcall orig-fun))
(funcall orig-fun)))
Nicer generated heading IDs
Thanks to alphapapa's unpackaged.el.
By default, url-hexify-string
seemed to cause me some issues. Replacing that in
a53899
resolved this for me. To go one step further, I create a function for
producing nice short links, like an inferior version of reftex-label
.
(defvar org-heading-contraction-max-words 3
"Maximum number of words in a heading")
(defvar org-heading-contraction-max-length 35
"Maximum length of resulting string")
(defvar org-heading-contraction-stripped-words
'("the" "on" "in" "off" "a" "for" "by" "of" "and" "is" "to")
"Unnecesary words to be removed from a heading")
(defun org-heading-contraction (heading-string)
"Get a contracted form of HEADING-STRING that is onlu contains alphanumeric charachters.
Strips 'joining' words in `org-heading-contraction-stripped-words',
and then limits the result to the first `org-heading-contraction-max-words' words.
If the total length is > `org-heading-contraction-max-length' then individual words are
truncated to fit within the limit"
(let ((heading-words
(-filter (lambda (word)
(not (member word org-heading-contraction-stripped-words)))
(split-string
(->> heading-string
s-downcase
(replace-regexp-in-string "\\[\\[[^]]+\\]\\[\\([^]]+\\)\\]\\]" "\\1") ; get description from org-link
(replace-regexp-in-string "[-/ ]+" " ") ; replace seperator-type chars with space
(replace-regexp-in-string "[^a-z0-9 ]" "") ; strip chars which need %-encoding in a uri
) " "))))
(when (> (length heading-words)
org-heading-contraction-max-words)
(setq heading-words
(subseq heading-words 0 org-heading-contraction-max-words)))
(when (> (+ (-sum (mapcar #'length heading-words))
(1- (length heading-words)))
org-heading-contraction-max-length)
;; trucate each word to a max word length determined by
;; max length = \floor{ \frac{total length - chars for seperators - \sum_{word \leq average length} length(word) }{num(words) > average length} }
(setq heading-words (let* ((total-length-budget (- org-heading-contraction-max-length ; how many non-separator chars we can use
(1- (length heading-words))))
(word-length-budget (/ total-length-budget ; max length of each word to keep within budget
org-heading-contraction-max-words))
(num-overlong (-count (lambda (word) ; how many words exceed that budget
(> (length word) word-length-budget))
heading-words))
(total-short-length (-sum (mapcar (lambda (word) ; total length of words under that budget
(if (<= (length word) word-length-budget)
(length word) 0))
heading-words)))
(max-length (/ (- total-length-budget total-short-length) ; max(max-length) that we can have to fit within the budget
num-overlong)))
(mapcar (lambda (word)
(if (<= (length word) max-length)
word
(substring word 0 max-length)))
heading-words))))
(string-join heading-words "-")))
Now here's alphapapa's subtley tweaked mode.
(define-minor-mode unpackaged/org-export-html-with-useful-ids-mode
"Attempt to export Org as HTML with useful link IDs.
Instead of random IDs like \"#orga1b2c3\", use heading titles,
made unique when necessary."
:global t
(if unpackaged/org-export-html-with-useful-ids-mode
(advice-add #'org-export-get-reference :override #'unpackaged/org-export-get-reference)
(advice-remove #'org-export-get-reference #'unpackaged/org-export-get-reference)))
(defun unpackaged/org-export-get-reference (datum info)
"Like `org-export-get-reference', except uses heading titles instead of random numbers."
(let ((cache (plist-get info :internal-references)))
(or (car (rassq datum cache))
(let* ((crossrefs (plist-get info :crossrefs))
(cells (org-export-search-cells datum))
;; Preserve any pre-existing association between
;; a search cell and a reference, i.e., when some
;; previously published document referenced a location
;; within current file (see
;; `org-publish-resolve-external-link').
;;
;; However, there is no guarantee that search cells are
;; unique, e.g., there might be duplicate custom ID or
;; two headings with the same title in the file.
;;
;; As a consequence, before re-using any reference to
;; an element or object, we check that it doesn't refer
;; to a previous element or object.
(new (or (cl-some
(lambda (cell)
(let ((stored (cdr (assoc cell crossrefs))))
(when stored
(let ((old (org-export-format-reference stored)))
(and (not (assoc old cache)) stored)))))
cells)
(when (org-element-property :raw-value datum)
;; Heading with a title
(unpackaged/org-export-new-title-reference datum cache))
;; NOTE: This probably breaks some Org Export
;; feature, but if it does what I need, fine.
(org-export-format-reference
(org-export-new-reference cache))))
(reference-string new))
;; Cache contains both data already associated to
;; a reference and in-use internal references, so as to make
;; unique references.
(dolist (cell cells) (push (cons cell new) cache))
;; Retain a direct association between reference string and
;; DATUM since (1) not every object or element can be given
;; a search cell (2) it permits quick lookup.
(push (cons reference-string datum) cache)
(plist-put info :internal-references cache)
reference-string))))
(defun unpackaged/org-export-new-title-reference (datum cache)
"Return new reference for DATUM that is unique in CACHE."
(cl-macrolet ((inc-suffixf (place)
`(progn
(string-match (rx bos
(minimal-match (group (1+ anything)))
(optional "--" (group (1+ digit)))
eos)
,place)
;; HACK: `s1' instead of a gensym.
(-let* (((s1 suffix) (list (match-string 1 ,place)
(match-string 2 ,place)))
(suffix (if suffix
(string-to-number suffix)
0)))
(setf ,place (format "%s--%s" s1 (cl-incf suffix)))))))
(let* ((title (org-element-property :raw-value datum))
;; get ascii-only form of title without needing percent-encoding
(ref (org-heading-contraction (substring-no-properties title)))
(parent (org-element-property :parent datum)))
(while (--any (equal ref (car it))
cache)
;; Title not unique: make it so.
(if parent
;; Append ancestor title.
(setf title (concat (org-element-property :raw-value parent)
"--" title)
;; get ascii-only form of title without needing percent-encoding
ref (org-heading-contraction (substring-no-properties title))
parent (org-element-property :parent parent))
;; No more ancestors: add and increment a number.
(inc-suffixf ref)))
ref)))
(add-hook 'org-load-hook #'unpackaged/org-export-html-with-useful-ids-mode)
Nicer org-return
Once again, from unpackaged.el
(after! org
(defun unpackaged/org-element-descendant-of (type element)
"Return non-nil if ELEMENT is a descendant of TYPE.
TYPE should be an element type, like `item' or `paragraph'.
ELEMENT should be a list like that returned by `org-element-context'."
;; MAYBE: Use `org-element-lineage'.
(when-let* ((parent (org-element-property :parent element)))
(or (eq type (car parent))
(unpackaged/org-element-descendant-of type parent))))
;;;###autoload
(defun unpackaged/org-return-dwim (&optional default)
"A helpful replacement for `org-return-indent'. With prefix, call `org-return-indent'.
On headings, move point to position after entry content. In
lists, insert a new item or end the list, with checkbox if
appropriate. In tables, insert a new row or end the table."
;; Inspired by John Kitchin: http://kitchingroup.cheme.cmu.edu/blog/2017/04/09/A-better-return-in-org-mode/
(interactive "P")
(if default
(org-return t)
(cond
;; Act depending on context around point.
;; NOTE: I prefer RET to not follow links, but by uncommenting this block, links will be
;; followed.
;; ((eq 'link (car (org-element-context)))
;; ;; Link: Open it.
;; (org-open-at-point-global))
((org-at-heading-p)
;; Heading: Move to position after entry content.
;; NOTE: This is probably the most interesting feature of this function.
(let ((heading-start (org-entry-beginning-position)))
(goto-char (org-entry-end-position))
(cond ((and (org-at-heading-p)
(= heading-start (org-entry-beginning-position)))
;; Entry ends on its heading; add newline after
(end-of-line)
(insert "\n\n"))
(t
;; Entry ends after its heading; back up
(forward-line -1)
(end-of-line)
(when (org-at-heading-p)
;; At the same heading
(forward-line)
(insert "\n")
(forward-line -1))
;; FIXME: looking-back is supposed to be called with more arguments.
(while (not (looking-back (rx (repeat 3 (seq (optional blank) "\n")))))
(insert "\n"))
(forward-line -1)))))
((org-at-item-checkbox-p)
;; Checkbox: Insert new item with checkbox.
(org-insert-todo-heading nil))
((org-in-item-p)
;; Plain list. Yes, this gets a little complicated...
(let ((context (org-element-context)))
(if (or (eq 'plain-list (car context)) ; First item in list
(and (eq 'item (car context))
(not (eq (org-element-property :contents-begin context)
(org-element-property :contents-end context))))
(unpackaged/org-element-descendant-of 'item context)) ; Element in list item, e.g. a link
;; Non-empty item: Add new item.
(org-insert-item)
;; Empty item: Close the list.
;; TODO: Do this with org functions rather than operating on the text. Can't seem to find the right function.
(delete-region (line-beginning-position) (line-end-position))
(insert "\n"))))
((when (fboundp 'org-inlinetask-in-task-p)
(org-inlinetask-in-task-p))
;; Inline task: Don't insert a new heading.
(org-return t))
((org-at-table-p)
(cond ((save-excursion
(beginning-of-line)
;; See `org-table-next-field'.
(cl-loop with end = (line-end-position)
for cell = (org-element-table-cell-parser)
always (equal (org-element-property :contents-begin cell)
(org-element-property :contents-end cell))
while (re-search-forward "|" end t)))
;; Empty row: end the table.
(delete-region (line-beginning-position) (line-end-position))
(org-return t))
(t
;; Non-empty row: call `org-return-indent'.
(org-return t))))
(t
;; All other cases: call `org-return-indent'.
(org-return t)))))
(advice-add #'org-return-indent :override #'unpackaged/org-return-dwim))
Visuals
In editor
Font Display
Mixed pitch is great. As is +org-pretty-mode
, let's use them.
(add-hook! 'org-mode-hook #'+org-pretty-mode #'mixed-pitch-mode)
Earlier I loaded the org-pretty-table
package, let's enable it everywhere!
(setq global-org-pretty-table-mode t)
Let's make headings a bit bigger
(custom-set-faces!
'(outline-1 :weight extra-bold :height 1.25)
'(outline-2 :weight bold :height 1.15)
'(outline-3 :weight bold :height 1.12)
'(outline-4 :weight semi-bold :height 1.09)
'(outline-5 :weight semi-bold :height 1.06)
'(outline-6 :weight semi-bold :height 1.03)
'(outline-8 :weight semi-bold)
'(outline-9 :weight semi-bold))
Symbols
It's also nice to change the character used for collapsed items (by default …
),
I think ▾
is better for indicating 'collapsed section'.
and add an extra org-bullet
to the default list of four.
I've also added some fun alternatives, just commented out.
;; (after! org
;; (use-package org-pretty-tags
;; :config
;; (setq org-pretty-tags-surrogate-strings
;; `(("uni" . ,(all-the-icons-faicon "graduation-cap" :face 'all-the-icons-purple :v-adjust 0.01))
;; ("ucc" . ,(all-the-icons-material "computer" :face 'all-the-icons-silver :v-adjust 0.01))
;; ("assignment" . ,(all-the-icons-material "library_books" :face 'all-the-icons-orange :v-adjust 0.01))
;; ("test" . ,(all-the-icons-material "timer" :face 'all-the-icons-red :v-adjust 0.01))
;; ("lecture" . ,(all-the-icons-fileicon "keynote" :face 'all-the-icons-orange :v-adjust 0.01))
;; ("email" . ,(all-the-icons-faicon "envelope" :face 'all-the-icons-blue :v-adjust 0.01))
;; ("read" . ,(all-the-icons-octicon "book" :face 'all-the-icons-lblue :v-adjust 0.01))
;; ("article" . ,(all-the-icons-octicon "file-text" :face 'all-the-icons-yellow :v-adjust 0.01))
;; ("web" . ,(all-the-icons-faicon "globe" :face 'all-the-icons-green :v-adjust 0.01))
;; ("info" . ,(all-the-icons-faicon "info-circle" :face 'all-the-icons-blue :v-adjust 0.01))
;; ("issue" . ,(all-the-icons-faicon "bug" :face 'all-the-icons-red :v-adjust 0.01))
;; ("someday" . ,(all-the-icons-faicon "calendar-o" :face 'all-the-icons-cyan :v-adjust 0.01))
;; ("idea" . ,(all-the-icons-octicon "light-bulb" :face 'all-the-icons-yellow :v-adjust 0.01))
;; ("emacs" . ,(all-the-icons-fileicon "emacs" :face 'all-the-icons-lpurple :v-adjust 0.01))))
;; (org-pretty-tags-global-mode)))
(after! org-superstar
(setq org-superstar-headline-bullets-list '("◉" "○" "✸" "✿" "✤" "✜" "◆" "▶")
;; org-superstar-headline-bullets-list '("Ⅰ" "Ⅱ" "Ⅲ" "Ⅳ" "Ⅴ" "Ⅵ" "Ⅶ" "Ⅷ" "Ⅸ" "Ⅹ")
org-superstar-prettify-item-bullets t ))
(after! org
(setq org-ellipsis " ▾ "
org-priority-highest ?A
org-priority-lowest ?E
org-priority-faces
'((?A . 'all-the-icons-red)
(?B . 'all-the-icons-orange)
(?C . 'all-the-icons-yellow)
(?D . 'all-the-icons-green)
(?E . 'all-the-icons-blue))))
It's also nice to make use of the Unicode characters for check boxes, and other commands.
(after! org
(appendq! +pretty-code-symbols
`(:checkbox "☐"
:pending "◼"
:checkedbox "☑"
:list_property "∷"
:results "🠶"
:property "☸"
:properties "⚙"
:end "∎"
:options "⌥"
:title "𝙏"
:author "𝘼"
:date "𝘿"
:latex_header "⇥"
:latex_class "🄲"
:begin_quote "❮"
:end_quote "❯"
:begin_export "⯮"
:end_export "⯬"
:priority_a ,(propertize "⚑" 'face 'all-the-icons-red)
:priority_b ,(propertize "⬆" 'face 'all-the-icons-orange)
:priority_c ,(propertize "■" 'face 'all-the-icons-yellow)
:priority_d ,(propertize "⬇" 'face 'all-the-icons-green)
:priority_e ,(propertize "❓" 'face 'all-the-icons-blue)
:em_dash "—"))
(set-pretty-symbols! 'org-mode
:merge t
:checkbox "[ ]"
:pending "[-]"
:checkedbox "[X]"
:list_property "::"
:results "#+RESULTS:"
:property "#+PROPERTY:"
:property ":PROPERTIES:"
:end ":END:"
:options "#+OPTIONS:"
:title "#+TITLE:"
:author "#+AUTHOR:"
:date "#+DATE:"
:latex_class "#+LATEX_CLASS:"
:latex_header "#+LATEX_HEADER:"
:begin_quote "#+BEGIN_QUOTE"
:end_quote "#+END_QUOTE"
:begin_export "#+BEGIN_EXPORT"
:end_export "#+END_EXPORT"
:priority_a "[#A]"
:priority_b "[#B]"
:priority_c "[#C]"
:priority_d "[#D]"
:priority_e "[#E]"
:em_dash "---"))
(plist-put +pretty-code-symbols :name "⁍") ; or › could be good?
We also like org-fragtog
, and that wants a hook.
(add-hook 'org-mode-hook 'org-fragtog-mode)
LaTeX Fragments
It's nice to customise the look of LaTeX fragments so they fit better in the text — like this \(\sqrt{\beta^2+3}-\sum_{\phi=1}^\infty \frac{x^\phi-1}{\Gamma(a)}\). Let's start by adding a sans font.
(setq org-format-latex-header "\\documentclass{article}
\\usepackage[usenames]{color}
\\usepackage[T1]{fontenc}
\\usepackage{mathtools}
\\usepackage{textcomp,amssymb}
\\usepackage[makeroom]{cancel}
\\pagestyle{empty} % do not remove
% The settings below are copied from fullpage.sty
\\setlength{\\textwidth}{\\paperwidth}
\\addtolength{\\textwidth}{-3cm}
\\setlength{\\oddsidemargin}{1.5cm}
\\addtolength{\\oddsidemargin}{-2.54cm}
\\setlength{\\evensidemargin}{\\oddsidemargin}
\\setlength{\\textheight}{\\paperheight}
\\addtolength{\\textheight}{-\\headheight}
\\addtolength{\\textheight}{-\\headsep}
\\addtolength{\\textheight}{-\\footskip}
\\addtolength{\\textheight}{-3cm}
\\setlength{\\topmargin}{1.5cm}
\\addtolength{\\topmargin}{-2.54cm}
% my custom stuff
\\usepackage{arev}
\\usepackage{arevmath}")
We can either render from a dvi
or pdf
file, so let's benchmark latex
and
pdflatex
.
latex time |
pdflatex time |
---|---|
135±2 ms | 215±3 ms |
On the rendering side, there are two .dvi
-to-image converters which I am
interested in: dvipng
and dvisvgm
. Then with the a .pdf
we have pdf2svg
.
For inline preview we care about speed, while for exporting we care about file
size and prefer a vector graphic.
Using the above latex expression and benchmarking lead to the following results:
dvipng time |
dvisvgm time |
pdf2svg time |
---|---|---|
89±2 ms | 178±2 ms | 12±2 ms |
Now let's combine this to see what's best
Tool chain | Total time | Resultant file size |
---|---|---|
latex + dvipng |
226±2 ms | 7 KiB |
latex + dvisvgm |
392±4 ms | 8 KiB |
pdflatex + pdf2svg |
230±2 ms | 16 KiB |
So, let's use dvipng
for previewing LaTeX fragments in-Emacs, but dvisvgm
for [[
Exporting to HTML][LaTeX Rendering]].
Unfortunately: it seems that svg sizing is annoying ATM, so let's actually not do this right now.
As well as having a sans font, there are a few other tweaks which can make them look better. Namely making sure that the colours switch when the theme does.
(after! org
;; make background of fragments transparent
;; (let ((dvipng--plist (alist-get 'dvipng org-preview-latex-process-alist)))
;; (plist-put dvipng--plist :use-xcolor t)
;; (plist-put dvipng--plist :image-converter '("dvipng -D %D -bg 'transparent' -T tight -o %O %f")))
(add-hook! 'doom-load-theme-hook
(defun +org-refresh-latex-background ()
(plist-put! org-format-latex-options
:background
(face-attribute (or (cadr (assq 'default face-remapping-alist))
'default)
:background nil t))))
)
It'd be nice to make mhchem
equations able to be rendered.
NB: This doesn't work at the moment.
(after! org
(add-to-list 'org-latex-regexps '("\\ce" "^\\\\ce{\\(?:[^\000{}]\\|{[^\000}]+?}\\)}" 0 nil)))
Stolen from scimax (semi-working right now)
I want fragment justification
(after! org
(defun scimax-org-latex-fragment-justify (justification)
"Justify the latex fragment at point with JUSTIFICATION.
JUSTIFICATION is a symbol for 'left, 'center or 'right."
(interactive
(list (intern-soft
(completing-read "Justification (left): " '(left center right)
nil t nil nil 'left))))
(let* ((ov (ov-at))
(beg (ov-beg ov))
(end (ov-end ov))
(shift (- beg (line-beginning-position)))
(img (overlay-get ov 'display))
(img (and (and img (consp img) (eq (car img) 'image)
(image-type-available-p (plist-get (cdr img) :type)))
img))
space-left offset)
(when (and img
;; This means the equation is at the start of the line
(= beg (line-beginning-position))
(or
(string= "" (s-trim (buffer-substring end (line-end-position))))
(eq 'latex-environment (car (org-element-context)))))
(setq space-left (- (window-max-chars-per-line) (car (image-size img)))
offset (floor (cond
((eq justification 'center)
(- (/ space-left 2) shift))
((eq justification 'right)
(- space-left shift))
(t
0))))
(when (>= offset 0)
(overlay-put ov 'before-string (make-string offset ?\ ))))))
(defun scimax-org-latex-fragment-justify-advice (beg end image imagetype)
"After advice function to justify fragments."
(scimax-org-latex-fragment-justify (or (plist-get org-format-latex-options :justify) 'left)))
(defun scimax-toggle-latex-fragment-justification ()
"Toggle if LaTeX fragment justification options can be used."
(interactive)
(if (not (get 'scimax-org-latex-fragment-justify-advice 'enabled))
(progn
(advice-add 'org--format-latex-make-overlay :after 'scimax-org-latex-fragment-justify-advice)
(put 'scimax-org-latex-fragment-justify-advice 'enabled t)
(message "Latex fragment justification enabled"))
(advice-remove 'org--format-latex-make-overlay 'scimax-org-latex-fragment-justify-advice)
(put 'scimax-org-latex-fragment-justify-advice 'enabled nil)
(message "Latex fragment justification disabled"))))
There's also this lovely equation numbering stuff I'll nick
;; Numbered equations all have (1) as the number for fragments with vanilla
;; org-mode. This code injects the correct numbers into the previews so they
;; look good.
(after! org
(defun scimax-org-renumber-environment (orig-func &rest args)
"A function to inject numbers in LaTeX fragment previews."
(let ((results '())
(counter -1)
(numberp))
(setq results (loop for (begin . env) in
(org-element-map (org-element-parse-buffer) 'latex-environment
(lambda (env)
(cons
(org-element-property :begin env)
(org-element-property :value env))))
collect
(cond
((and (string-match "\\\\begin{equation}" env)
(not (string-match "\\\\tag{" env)))
(incf counter)
(cons begin counter))
((string-match "\\\\begin{align}" env)
(prog2
(incf counter)
(cons begin counter)
(with-temp-buffer
(insert env)
(goto-char (point-min))
;; \\ is used for a new line. Each one leads to a number
(incf counter (count-matches "\\\\$"))
;; unless there are nonumbers.
(goto-char (point-min))
(decf counter (count-matches "\\nonumber")))))
(t
(cons begin nil)))))
(when (setq numberp (cdr (assoc (point) results)))
(setf (car args)
(concat
(format "\\setcounter{equation}{%s}\n" numberp)
(car args)))))
(apply orig-func args))
(defun scimax-toggle-latex-equation-numbering ()
"Toggle whether LaTeX fragments are numbered."
(interactive)
(if (not (get 'scimax-org-renumber-environment 'enabled))
(progn
(advice-add 'org-create-formula-image :around #'scimax-org-renumber-environment)
(put 'scimax-org-renumber-environment 'enabled t)
(message "Latex numbering enabled"))
(advice-remove 'org-create-formula-image #'scimax-org-renumber-environment)
(put 'scimax-org-renumber-environment 'enabled nil)
(message "Latex numbering disabled.")))
(advice-add 'org-create-formula-image :around #'scimax-org-renumber-environment)
(put 'scimax-org-renumber-environment 'enabled t))
Exporting (general)
(after! org (setq org-export-headline-levels 5)) ; I like nesting
I'm also going to make use of an item in ox-extra
so that I can add an :ignore:
tag to headings for the content to be kept, but the heading itself ignored
(unlike :noexport:
which ignored both heading and content). This is useful when
I want to use headings to provide a structure for writing that doesn't appear in
the final documents.
(after! org
(require 'ox-extra)
(ox-extras-activate '(ignore-headlines)))
Exporting to HTML
Custom CSS/JS
There is a fantastic exporter config (fniessen/org-html-themes) which we can setup to be used with all our org files. Since most of the syntax highlighting colours from our /tec/emacs-config/src/commit/620f2c57d0830ef7c8582cc593834c8668ec19d5/Theme gets used, we benefit from customising the code block style.
We also want to make the background and foreground colours of the <pre>
blocks
match out theme (they don't by default), so I scraped some code from emacs.stackexchange
.
(defun my-org-inline-css-hook (exporter)
"Insert custom inline css to automatically set the
background of code to whatever theme I'm using's background"
(when (eq exporter 'html)
(setq
org-html-head-extra
(concat
(if (s-contains-p "<!––tec/custom-head-start-->" org-html-head-extra)
(s-replace-regexp "<!––tec\\/custom-head-start-->[^🙜]*<!––tec\\/custom-head-end-->" "" org-html-head-extra)
org-html-head-extra)
(format "<!––tec/custom-head-start-->
<style type=\"text/css\">
:root {
--theme-bg: %s;
--theme-bg-alt: %s;
--theme-base0: %s;
--theme-base1: %s;
--theme-base2: %s;
--theme-base3: %s;
--theme-base4: %s;
--theme-base5: %s;
--theme-base6: %s;
--theme-base7: %s;
--theme-base8: %s;
--theme-fg: %s;
--theme-fg-alt: %s;
--theme-grey: %s;
--theme-red: %s;
--theme-orange: %s;
--theme-green: %s;
--theme-teal: %s;
--theme-yellow: %s;
--theme-blue: %s;
--theme-dark-blue: %s;
--theme-magenta: %s;
--theme-violet: %s;
--theme-cyan: %s;
--theme-dark-cyan: %s;
}
</style>"
(doom-color 'bg)
(doom-color 'bg-alt)
(doom-color 'base0)
(doom-color 'base1)
(doom-color 'base2)
(doom-color 'base3)
(doom-color 'base4)
(doom-color 'base5)
(doom-color 'base6)
(doom-color 'base7)
(doom-color 'base8)
(doom-color 'fg)
(doom-color 'fg-alt)
(doom-color 'grey)
(doom-color 'red)
(doom-color 'orange)
(doom-color 'green)
(doom-color 'teal)
(doom-color 'yellow)
(doom-color 'blue)
(doom-color 'dark-blue)
(doom-color 'magenta)
(doom-color 'violet)
(doom-color 'cyan)
(doom-color 'dark-cyan))
"
<<orgHtmlStyle>>
<!––tec/custom-head-end-->
"
))))
(add-hook 'org-export-before-processing-hook 'my-org-inline-css-hook)
Make verbatim different to code
Since we have verbatim
and code
, let's use verbatim
for key strokes.
(setq org-html-text-markup-alist
'((bold . "<b>%s</b>")
(code . "<code>%s</code>")
(italic . "<i>%s</i>")
(strike-through . "<del>%s</del>")
(underline . "<span class=\"underline\">%s</span>")
(verbatim . "<kbd>%s</kbd>")))
Change checkbox type
We also want to use HTML checkboxes, however we want to get a bit fancier than default
(after! org
(appendq! org-html-checkbox-types '((html-span .
((on . "<span class='checkbox'></span>")
(off . "<span class='checkbox'></span>")
(trans . "<span class='checkbox'></span>")))))
(setq org-html-checkbox-type 'html-span))
- I'm yet to do this
- Work in progress
- This is done
LaTeX Rendering
On the maths side of things, I consider dvisvgm
to be a rather compelling
option. However this isn't sized very well at the moment.
;; (setq-default org-html-with-latex `dvisvgm)
Exporting to LaTeX
I like automatically using spaced small caps for acronyms. For strings I want to
be unaffected lest's use ;
as a prefix to prevent the transformation — i.e.
;JFK
(as one would want for two-letter geographic locations and names).
;; TODO make this /only/ apply to text (i.e. not URL)
(after! org
(defun tec/org-export-latex-filter-acronym (text backend info)
(when (org-export-derived-backend-p backend 'latex)
(let ((case-fold-search nil))
(replace-regexp-in-string
"[;\\\\]?\\b[A-Z][A-Z]+s?"
(lambda (all-caps-str)
; only \acr if str doesn't start with ";" or "\" (for LaTeX commands)
(cond ((equal (aref all-caps-str 0) ?\;) (substring all-caps-str 1))
((equal (aref all-caps-str 0) ?\\) all-caps-str)
((equal (aref all-caps-str (- (length all-caps-str) 1)) ?s)
(concat "\\textls*[70]{\\textsc{"
(s-downcase (substring all-caps-str 0 -1))
"}\\protect\\scalebox{.91}[.84]{s}}"))
(t (concat "\\textls*[70]{\\textsc{"
(s-downcase all-caps-str) "}}"))))
text t t))))
(add-to-list 'org-export-filter-plain-text-functions
'tec/org-export-latex-filter-acronym)
(add-to-list 'org-export-filter-headline-functions
'tec/org-export-latex-filter-acronym))
Now for a few more adjustments.
(after! ox-latex
(add-to-list 'org-latex-classes
'("fancy-article"
"\\documentclass{scrartcl}\n\
\\usepackage[T1]{fontenc}\n\
\\usepackage[osf,largesc,helvratio=0.9]{newpxtext}\n\
\\usepackage[scale=0.92]{sourcecodepro}\n\
\\usepackage[varbb]{newpxmath}\n\
\\usepackage[activate={true,nocompatibility},final,tracking=true,kerning=true,spacing=true,factor=2000]{microtype}\n\
\\usepackage{xcolor}\n\
\\usepackage{booktabs}
\\usepackage{subcaption}
\\usepackage[hypcap=true]{caption}
\\setkomafont{caption}{\\sffamily\\small}
\\setkomafont{captionlabel}{\\upshape\\bfseries}
\\captionsetup{justification=raggedright,singlelinecheck=true}
\\setcapindent{0pt}
\\setlength{\\parskip}{\\baselineskip}\n\
\\setlength{\\parindent}{0pt}"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(add-to-list 'org-latex-classes
'("blank"
"[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(add-to-list 'org-latex-classes
'("bmc-article"
"\\documentclass[article,code,maths]{bmc}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]
[EXTRA]"
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(add-to-list 'org-latex-classes
'("bmc"
"\\documentclass[code,maths]{bmc}
[NO-DEFAULT-PACKAGES]
[NO-PACKAGES]
[EXTRA]"
("\\chapter{%s}" . "\\chapter*{%s}")
("\\section{%s}" . "\\section*{%s}")
("\\subsection{%s}" . "\\subsection*{%s}")
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
("\\paragraph{%s}" . "\\paragraph*{%s}")
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
(setq org-latex-default-class "fancy-article")
(add-to-list 'org-latex-packages-alist '("" "minted"))
(setq org-latex-listings 'minted
org-latex-minted-options
'(("frame" "lines")
("fontsize" "\\scriptsize")
("linenos" "")
("breakanywhere" "true")
("breakautoindent" "true")
("breaklines" "true")
("autogobble" "true")
("obeytabs" "true")
("python3" "true")
("breakbefore" "\\\\\\.+")
("breakafter" "\\,")
("style" "autumn")
("breaksymbol" "\\tiny\\ensuremath{\\hookrightarrow}")
("breakanywheresymbolpre" "\\,\\footnotesize\\ensuremath{{}_{\\rfloor}}")
("breakbeforesymbolpre" "\\,\\footnotesize\\ensuremath{{}_{\\rfloor}}")
("breakaftersymbolpre" "\\,\\footnotesize\\ensuremath{{}_{\\rfloor}}")))
(setq org-latex-tables-booktabs t)
(setq org-latex-hyperref-template "
\\colorlet{greenyblue}{blue!70!green}
\\colorlet{blueygreen}{blue!40!green}
\\providecolor{link}{named}{greenyblue}
\\providecolor{cite}{named}{blueygreen}
\\hypersetup{
pdfauthor={%a},
pdftitle={%t},
pdfkeywords={%k},
pdfsubject={%d},
pdfcreator={%c},
pdflang={%L},
breaklinks=true,
colorlinks=true,
linkcolor=,
urlcolor=link,
citecolor=cite\n}
\\urlstyle{same}\n")
(setq org-latex-pdf-process
'("latexmk -shell-escape -interaction=nonstopmode -f -pdf -output-directory=%o %f")))
Chameleon — aka. match theme
Once the idea of having the look of the LaTeX document produced match the
current Emacs theme, I was enraptured. The result is the pseudo-class chameleon
.
(after! ox
(defvar ox-chameleon-base-class "fancy-article"
"The base class that chameleon builds on")
(defvar ox-chameleon--p nil
"Used to indicate whether the current export is trying to blend in. Set just before being accessed.")
;; (setf (alist-get :filter-latex-class
;; (org-export-backend-filters
;; (org-export-get-backend 'latex)))
;; 'ox-chameleon-latex-class-detector-filter)
;; (defun ox-chameleon-latex-class-detector-filter (info backend)
;; ""
;; (setq ox-chameleon--p (when (equal (plist-get info :latex-class)
;; "chameleon")
;; (plist-put info :latex-class ox-chameleon-base-class)
;; t)))
;; TODO make this less hacky. One ideas was as follows
;; (map-put (org-export-backend-filters (org-export-get-backend 'latex))
;; :filter-latex-class 'ox-chameleon-latex-class-detector-filter))
;; Never seemed to execute though
(defadvice! ox-chameleon-org-latex-detect (orig-fun info)
:around #'org-export-install-filters
(setq ox-chameleon--p (when (equal (plist-get info :latex-class)
"chameleon")
(plist-put info :latex-class ox-chameleon-base-class)
t))
(funcall orig-fun info))
(defadvice! ox-chameleon-org-latex-export (orig-fn info &optional template snippet?)
:around #'org-latex-make-preamble
(funcall orig-fn info)
(if (not ox-chameleon--p)
(funcall orig-fn info template snippet?)
(concat (funcall orig-fn info template snippet?)
(ox-chameleon-generate-colourings))))
(defun ox-chameleon-generate-colourings ()
(apply #'format
"%% make document follow Emacs theme
\\definecolor{bg}{HTML}{%s}
\\definecolor{fg}{HTML}{%s}
\\definecolor{red}{HTML}{%s}
\\definecolor{orange}{HTML}{%s}
\\definecolor{green}{HTML}{%s}
\\definecolor{teal}{HTML}{%s}
\\definecolor{yellow}{HTML}{%s}
\\definecolor{blue}{HTML}{%s}
\\definecolor{dark-blue}{HTML}{%s}
\\definecolor{magenta}{HTML}{%s}
\\definecolor{violet}{HTML}{%s}
\\definecolor{cyan}{HTML}{%s}
\\definecolor{dark-cyan}{HTML}{%s}
\\definecolor{level1}{HTML}{%s}
\\definecolor{level2}{HTML}{%s}
\\definecolor{level3}{HTML}{%s}
\\definecolor{level4}{HTML}{%s}
\\definecolor{level5}{HTML}{%s}
\\definecolor{level6}{HTML}{%s}
\\definecolor{level7}{HTML}{%s}
\\definecolor{level8}{HTML}{%s}
\\definecolor{link}{HTML}{%s}
\\definecolor{cite}{HTML}{%s}
\\definecolor{itemlabel}{HTML}{%s}
\\definecolor{code}{HTML}{%s}
\\definecolor{verbatim}{HTML}{%s}
\\pagecolor{bg}
\\color{fg}
\\addtokomafont{section}{\\color{level1}}
\\newkomafont{sectionprefix}{\\color{level1}}
\\addtokomafont{subsection}{\\color{level2}}
\\newkomafont{subsectionprefix}{\\color{level2}}
\\addtokomafont{subsubsection}{\\color{level3}}
\\newkomafont{subsubsectionprefix}{\\color{level3}}
\\addtokomafont{paragraph}{\\color{level4}}
\\newkomafont{paragraphprefix}{\\color{level4}}
\\addtokomafont{subparagraph}{\\color{level5}}
\\newkomafont{subparagraphprefix}{\\color{level5}}
\\renewcommand{\\labelitemi}{\\textcolor{itemlabel}{\\textbullet}}
\\renewcommand{\\labelitemii}{\\textcolor{itemlabel}{\\normalfont\\bfseries \\textendash}}
\\renewcommand{\\labelitemiii}{\\textcolor{itemlabel}{\\textasteriskcentered}}
\\renewcommand{\\labelitemiv}{\\textcolor{itemlabel}{\\textperiodcentered}}
\\renewcommand{\\labelenumi}{\\textcolor{itemlabel}{\\theenumi.}}
\\renewcommand{\\labelenumii}{\\textcolor{itemlabel}{(\\theenumii)}}
\\renewcommand{\\labelenumiii}{\\textcolor{itemlabel}{\\theenumiii.}}
\\renewcommand{\\labelenumiv}{\\textcolor{itemlabel}{\\theenumiv.}}
\\DeclareTextFontCommand{\\texttt}{\\color{code}\\ttfamily}
\\makeatletter
\\def\\verbatim@font{\\color{verbatim}\\normalfont\\ttfamily}
\\makeatother
%% end customisations
"
(mapcar (doom-rpartial #'substring 1)
(list
(face-attribute 'solaire-default-face :background)
(face-attribute 'default :foreground)
;;
(doom-color 'red)
(doom-color 'orange)
(doom-color 'green)
(doom-color 'teal)
(doom-color 'yellow)
(doom-color 'blue)
(doom-color 'dark-blue)
(doom-color 'magenta)
(doom-color 'violet)
(doom-color 'cyan)
(doom-color 'dark-cyan)
;;
(face-attribute 'outline-1 :foreground)
(face-attribute 'outline-2 :foreground)
(face-attribute 'outline-3 :foreground)
(face-attribute 'outline-4 :foreground)
(face-attribute 'outline-5 :foreground)
(face-attribute 'outline-6 :foreground)
(face-attribute 'outline-7 :foreground)
(face-attribute 'outline-8 :foreground)
;;
(face-attribute 'link :foreground)
(or (face-attribute 'org-ref-cite-face :foreground) (doom-color 'yellow))
(face-attribute 'org-list-dt :foreground)
(face-attribute 'org-code :foreground)
(face-attribute 'org-verbatim :foreground)
))))
)
Make verbatim different to code
Since have just gone to so much effort above let's make the most of it by making
verbatim
use verb
instead of protectedtexttt
(default).
(setq org-latex-text-markup-alist '((bold . "\\textbf{%s}")
(code . protectedtexttt)
(italic . "\\emph{%s}")
(strike-through . "\\sout{%s}")
(underline . "\\uline{%s}")
(verbatim . verb)))
Exporting to Beamer
It's nice to use a different theme
(setq org-beamer-theme "[progressbar=foot]metropolis")
Then customise it a bit
And I think that it's natural to divide a presentation into sections, e.g.
Introduction, Overview… so let's set bump up the headline level that becomes a
frame from 1
to 2
.
(setq org-beamer-frame-level 2)
Exporting to GFM
We just need to load ox-gfm
for org-mode documents
(eval-after-load "org"
'(require 'ox-gfm nil t))
Babel
Doom lazy-loads babel languages, with is lovely.
We need to tell babel to use python3. Who uses python2 anymore anyway? And why
doesn't python
refer to the latest version!?
(setq org-babel-python-command "python3")
We also like auto-completion here
(defun tec-org-python ()
(if (eq major-mode 'python-mode)
(progn (anaconda-mode t)
(company-mode t)))
)
(add-hook 'org-src-mode-hook 'tec-org-python)
ESS
We don't want R
evaluation to hang the editor, hence
(setq ess-eval-visibly 'nowait)
Syntax highlighting is nice, so let's turn all of that on
(setq ess-R-font-lock-keywords '((ess-R-fl-keyword:keywords . t)
(ess-R-fl-keyword:constants . t)
(ess-R-fl-keyword:modifiers . t)
(ess-R-fl-keyword:fun-defs . t)
(ess-R-fl-keyword:assign-ops . t)
(ess-R-fl-keyword:%op% . t)
(ess-fl-keyword:fun-calls . t)
(ess-fl-keyword:numbers . t)
(ess-fl-keyword:operators . t)
(ess-fl-keyword:delimiters . t)
(ess-fl-keyword:= . t)
(ess-R-fl-keyword:F&T . t)))
LaTeX
To-be-implemented ideas
-
Paste image from clipboard
- Determine first folder in
graphicspath
if applicable - Ask for file name
-
Use
xclip
to save file to graphics folder, or current directory (whichever applies)command -v xclip >/dev/null 2>&1 || { echo >&1 "no xclip"; exit 1; } if xclip -selection clipboard -target image/png -o >/dev/null 2>&1 then xclip -selection clipboard -target image/png -o >$1 2>/dev/null echo $1 else echo "no image" fi
- Insert figure, with filled in details as a result (activate
yasnippet
with filename as variable maybe?)
- Determine first folder in
Compilation
(setq TeX-save-query nil
TeX-show-compilation t
TeX-command-extra-options "-shell-escape")
(after! latex
(add-to-list 'TeX-command-list '("XeLaTeX" "%`xelatex%(mode)%' %t" TeX-run-TeX nil t)))
Snippet value
For use in the new-file template, let's set out a nice preamble we may want to use.
\\usepackage[pdfa,unicode=true,hidelinks]{hyperref}
\\usepackage[dvipsnames,svgnames,table,hyperref]{xcolor}
\\renewcommand{\\UrlFont}{\\ttfamily\\small}
\\usepackage[a-2b]{pdfx} % why not be archival
\\usepackage[T1]{fontenc}
\\usepackage[osf,helvratio=0.9]{newpxtext} % pallatino
\\usepackage[scale=0.92]{sourcecodepro}
\\usepackage[varbb]{newpxmath}
\\usepackage{mathtools}
\\usepackage{amssymb}
\\usepackage[activate={true,nocompatibility},final,tracking=true,kerning=true,spacing=true,factor=2000]{microtype}
% microtype makes text look nicer
\\usepackage{graphicx} % include graphics
\\usepackage{grffile} % fix allowed graphicx filenames
\\usepackage{booktabs} % nice table rules
Then let's bind the content to a function, and define some nice helpers.
(setq tec/yas-latex-template-preamble "
<<latex-nice-preable>>
")
(defun tec/yas-latex-get-class-choice ()
"Prompt user for LaTeX class choice"
(setq tec/yas-latex-class-choice (ivy-read "Select document class: " '("article" "scrartcl" "bmc") :def "bmc")))
(defun tec/yas-latex-preamble-if ()
"Based on class choice prompt for insertion of default preamble"
(if (equal tec/yas-latex-class-choice "bmc") 'nil
(eq (read-char-choice "Include default preamble? [Type y/n]" '(?y ?n)) ?y)))
Editor visuals
Once again, all hail mixed pitch mode!
(add-hook 'LaTeX-mode-hook #'mixed-pitch-mode)
Let's enhance TeX-fold-math
a bit
(after! latex
(setcar (assoc "⋆" LaTeX-fold-math-spec-list) "★")) ;; make \star bigger
(setq TeX-fold-math-spec-list
`(;; missing/better symbols
("≤" ("le"))
("≥" ("ge"))
("≠" ("ne"))
;; conviniance shorts
("‹" ("left"))
("›" ("right"))
;; private macros
("ℝ" ("RR"))
("ℕ" ("NN"))
("ℤ" ("ZZ"))
("ℚ" ("QQ"))
("ℂ" ("CC"))
("ℙ" ("PP"))
("ℍ" ("HH"))
("𝔼" ("EE"))
("𝑑" ("dd"))
;; known commands
("" ("phantom"))
(,(lambda (num den) (if (and (TeX-string-single-token-p num) (TeX-string-single-token-p den))
(concat num "/" den)
(concat "❪" num "/" den "❫"))) ("frac"))
(,(lambda (arg) (concat "√" (TeX-fold-parenthesize-as-neccesary arg))) ("sqrt"))
(,(lambda (arg) (concat "⭡" (TeX-fold-parenthesize-as-neccesary arg))) ("vec"))
("‘{1}’" ("text"))
;; private commands
("|{1}|" ("abs"))
("‖{1}‖" ("norm"))
("⌊{1}⌋" ("floor"))
("⌈{1}⌉" ("ceil"))
("⌊{1}⌉" ("round"))
("𝑑{1}/𝑑{2}" ("dv"))
("∂{1}/∂{2}" ("pdv"))
;; fancification
("{1}" ("mathrm"))
(,(lambda (word) (string-offset-roman-chars 119743 word)) ("mathbf"))
(,(lambda (word) (string-offset-roman-chars 119951 word)) ("mathcal"))
(,(lambda (word) (string-offset-roman-chars 120003 word)) ("mathfrak"))
(,(lambda (word) (string-offset-roman-chars 120055 word)) ("mathbb"))
(,(lambda (word) (string-offset-roman-chars 120159 word)) ("mathsf"))
(,(lambda (word) (string-offset-roman-chars 120367 word)) ("mathtt"))
)
TeX-fold-macro-spec-list
'(
;; as the defaults
("[f]" ("footnote" "marginpar"))
("[c]" ("cite"))
("[l]" ("label"))
("[r]" ("ref" "pageref" "eqref"))
("[i]" ("index" "glossary"))
("..." ("dots"))
("{1}" ("emph" "textit" "textsl" "textmd" "textrm" "textsf" "texttt"
"textbf" "textsc" "textup"))
;; tweaked defaults
("©" ("copyright"))
("®" ("textregistered"))
("™" ("texttrademark"))
("[1]:||►" ("item"))
("❡❡ {1}" ("part" "part*"))
("❡ {1}" ("chapter" "chapter*"))
("§ {1}" ("section" "section*"))
("§§ {1}" ("subsection" "subsection*"))
("§§§ {1}" ("subsubsection" "subsubsection*"))
("¶ {1}" ("paragraph" "paragraph*"))
("¶¶ {1}" ("subparagraph" "subparagraph*"))
;; extra
("⬖ {1}" ("begin"))
("⬗ {1}" ("end"))
))
(defun string-offset-roman-chars (offset word)
"Shift the codepoint of each charachter in WORD by OFFSET with an extra -6 shift if the letter is lowercase"
(apply 'string
(mapcar (lambda (c) (+ (if (>= c 97) (- c 6) c) offset)) word)))
(defun TeX-fold-parenthesize-as-neccesary (tokens &optional suppress-left suppress-right)
"Add ❪ ❫ parenthesis as if multiple LaTeX tokens appear to be present"
(if (TeX-string-single-token-p tokens) tokens
(concat (if suppress-left "" "❪")
tokens
(if suppress-right "" "❫"))))
(defun TeX-string-single-token-p (teststring)
"Return t if TESTSTRING appears to be a single token, nil otherwise"
(if (string-match-p "^\\\\?\\w+$" teststring) t nil))
Some local keybindings to make life a bit easier
(after! tex
(map!
:map LaTeX-mode-map
:ei [C-return] #'LaTeX-insert-item
;; normal stuff here
:localleader
:desc "View" "v" #'TeX-view)
(setq TeX-electric-math '("\\(" . "")))
Maths deliminators can be de-emphasised a bit
;; Making \( \) less visible
(defface unimportant-latex-face
'((t
:inherit font-lock-comment-face :family "Overpass" :weight light))
"Face used to make \\(\\), \\[\\] less visible."
:group 'LaTeX-math)
(font-lock-add-keywords
'latex-mode
`((,(rx (and "\\" (any "()[]"))) 0 'unimportant-latex-face prepend))
'end)
(font-lock-add-keywords
'latex-mode
`((,"\\\\[[:word:]]+" 0 'font-lock-keyword-face prepend))
'end)
And enable shell escape for the preview
(setq preview-LaTeX-command '("%`%l \"\\nonstopmode\\nofiles\
\\PassOptionsToPackage{" ("," . preview-required-option-list) "}{preview}\
\\AtBeginDocument{\\ifx\\ifPreview\\undefined"
preview-default-preamble "\\fi}\"%' \"\\detokenize{\" %t \"}\""))
CDLaTeX
The symbols and modifies are very nice by default, but could do with a bit of
fleshing out. Let's change the prefix to a key which is similarly rarely used,
but more convenient, like ;
.
(after! cdlatex
(setq ;; cdlatex-math-symbol-prefix ?\; ;; doesn't work at the moment :(
cdlatex-math-symbol-alist
'( ;; adding missing functions to 3rd level symbols
(?_ ("\\downarrow" "" "\\inf"))
(?2 ("^2" "\\sqrt{?}" "" ))
(?3 ("^3" "\\sqrt[3]{?}" "" ))
(?^ ("\\uparrow" "" "\\sup"))
(?k ("\\kappa" "" "\\ker"))
(?m ("\\mu" "" "\\lim"))
(?c ("" "\\circ" "\\cos"))
(?d ("\\delta" "\\partial" "\\dim"))
(?D ("\\Delta" "\\nabla" "\\deg"))
;; no idea why \Phi isnt on 'F' in first place, \phi is on 'f'.
(?F ("\\Phi"))
;; now just conveniance
(?. ("\\cdot" "\\dots"))
(?: ("\\vdots" "\\ddots"))
(?* ("\\times" "\\star" "\\ast")))
cdlatex-math-modify-alist
'( ;; my own stuff
(?B "\\mathbb" nil t nil nil)
(?a "\\abs" nil t nil nil))))
In a smilar vein to this, really whenever I have a number after a symbol (letter or command) I want it as a subscript. @yoavm448 did some lovely legwork to provide this:
(defun prvt/auto-number-subscript ()
(interactive)
(if (and (or (and (>= (char-before) ?a) (<= (char-before) ?z))
(and (>= (char-before) ?A) (<= (char-before) ?Z)))
(cl-digit-char-p (string-to-char (this-command-keys)))
(texmathp))
(insert "_" (this-command-keys))
(insert (this-command-keys))))
(map!
:after tex :map LaTeX-mode-map
:i "1" #'prvt/auto-number-subscript
:i "2" #'prvt/auto-number-subscript
:i "3" #'prvt/auto-number-subscript
:i "4" #'prvt/auto-number-subscript
:i "5" #'prvt/auto-number-subscript
:i "6" #'prvt/auto-number-subscript
:i "7" #'prvt/auto-number-subscript
:i "8" #'prvt/auto-number-subscript
:i "9" #'prvt/auto-number-subscript)
SyncTeX
(after! tex
(add-to-list 'TeX-view-program-list '("Evince" "evince %o"))
(add-to-list 'TeX-view-program-selection '(output-pdf "Evince")))
Python
Since I'm using mypyls
, as suggested in :lang python LSP support I'll tweak the
priority of mypyls
(after! lsp-python-ms
(set-lsp-priority! 'mspyls 1))
R
Editor Visuals
(after! ess-r-mode
(appendq! +pretty-code-symbols
'(:assign "⟵"
:multiply "×"))
(set-pretty-symbols! 'ess-r-mode
;; Functional
:def "function"
;; Types
:null "NULL"
:true "TRUE"
:false "FALSE"
:int "int"
:floar "float"
:bool "bool"
;; Flow
:not "!"
:and "&&" :or "||"
:for "for"
:in "%in%"
:return "return"
;; Other
:assign "<-"
:multiply "%*%"))
hledger
ledger-mode
is great and all, but hledger
seems to be more actively maintained.
For example, from 2018–2020, the most prolific contributor to ledger
produced
31 commits. For hledger
this statistic is 1800 commits. In addition, over the
last decade, ledger
seems to have lost steam, while hledger
seems as actively
developed as ever. From this basic comparison hledger
looks to have a more
promising outlook. It also has a few extra niceties that ledger
doesn't, but is
a little slower (haskell
vs. c++
).
Since this uses the same format, and ledger-mode
is well integrated into emacs,
and produced by John Wiegley — author of ledger
and current Emacs maintainer
— using this seems like a good idea. Thankfully we can, with a little modification.
(setq ledger-mode-should-check-version nil
ledger-report-links-in-register nil
ledger-binary-path "hledger")
Markdown
Let's use mixed pitch, because it's great
(add-hook! (gfm-mode markdown-mode) #'mixed-pitch-mode)
Most of the time when I write markdown, it's going into some app/website which will do it's own line wrapping, hence we only want to use visual line wrapping. No hard stuff.
(add-hook! (gfm-mode markdown-mode) #'visual-line-mode #'turn-off-auto-fill)
Beancount
The beancount package online has been put into ./lisp
, we just need to load and
enable it for .beancount
files.
(use-package! beancount
:load-path "~/.config/doom/lisp"
:mode ("\\.beancount\\'" . beancount-mode)
:config
(setq beancount-electric-currency t)
(defun beancount-bal ()
"Run bean-report bal."
(interactive)
(let ((compilation-read-command nil))
(beancount--run "bean-report"
(file-relative-name buffer-file-name) "bal")))
;; TODO make the following *work*
:bind (:map beancount-mode-map ("S-RET" . #'beancount-align-to-previous-number)))