2849 lines
117 KiB
Org Mode
2849 lines
117 KiB
Org Mode
#+title: DOOM Emacs
|
||
#+subtitle: Copyright (C) 2025 ─ Vishakh Kumar, Shaurya Singh, Henrik Lissner
|
||
#+author: Vishakh Kumar
|
||
#+description: A GNU Emacs configuration
|
||
#+startup: show2levels indent hidestars
|
||
#+options: coverpage:yes
|
||
#+property: header-args:emacs-lisp :tangle yes :comments link
|
||
|
||
#+begin_quote
|
||
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. @@latex:\mbox{@@--- Donald Knuth@@latex:}@@
|
||
#+end_quote
|
||
|
||
* Doom Configuration
|
||
** Modules
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Doom has this lovely /modular configuration base/ that takes a lot of work out of
|
||
configuring Emacs. Each module (when enabled) can provide a list of packages to
|
||
install (on ~doom sync~) and configuration to be applied. The modules can also
|
||
have flags applied to tweak their behaviour.
|
||
|
||
#+name: init.el
|
||
#+attr_html: :collapsed t
|
||
#+begin_src emacs-lisp :tangle "init.el" :noweb no-export :comments no
|
||
;;; init.el -*- lexical-binding: t; -*-
|
||
|
||
;; This file controls what Doom modules are enabled and what order they load in.
|
||
;; Press 'K' on a module to view its documentation, and 'gd' to browse its directory.
|
||
|
||
(doom! :completion
|
||
<<doom-completion>>
|
||
|
||
:ui
|
||
<<doom-ui>>
|
||
|
||
:editor
|
||
<<doom-editor>>
|
||
|
||
:emacs
|
||
<<doom-emacs>>
|
||
|
||
:term
|
||
<<doom-term>>
|
||
|
||
:checkers
|
||
<<doom-checkers>>
|
||
|
||
:tools
|
||
<<doom-tools>>
|
||
|
||
:os
|
||
<<doom-os>>
|
||
|
||
:lang
|
||
<<doom-lang>>
|
||
|
||
:email
|
||
<<doom-email>>
|
||
|
||
:app
|
||
<<doom-app>>
|
||
|
||
:config
|
||
<<doom-config>>)
|
||
#+end_src
|
||
|
||
***** Structure
|
||
As you may have noticed by this point, this is a [[https://en.wikipedia.org/wiki/Literate_programming][literate]] configuration. Doom
|
||
has good support for this which we access though the ~literate~ module.
|
||
|
||
While we're in the src_elisp{:config} section, we'll use Dooms nicer defaults,
|
||
along with the bindings and smartparens behaviour (the flags aren't documented,
|
||
but they exist).
|
||
#+name: doom-config
|
||
#+begin_src emacs-lisp
|
||
literate
|
||
(default +bindings +smartparens)
|
||
#+end_src
|
||
|
||
***** Interface
|
||
There's a lot that can be done to enhance Emacs' capabilities.
|
||
I reckon enabling half the modules Doom provides should do it.
|
||
#+name: doom-completion
|
||
#+begin_src emacs-lisp
|
||
(company ; the ultimate code completion backend
|
||
+childframe) ; ... when your children are better than you
|
||
(vertico +icons) ; the search engine of the future
|
||
#+end_src
|
||
|
||
#+name: doom-ui
|
||
#+begin_src emacs-lisp
|
||
doom-dashboard ; a nifty splash screen for Emacs
|
||
doom-quit ; DOOM quit-message prompts when you quit Emacs
|
||
(ligatures ; ligatures and symbols to make your code pnoretty again
|
||
+extra) ; for those who dislike letters
|
||
minimap ; show a map of the code on the side
|
||
ophints ; highlight the region an operation acts on
|
||
(popup ; tame sudden yet inevitable temporary windows
|
||
+all ; catch all popups that start with an asterix
|
||
+defaults) ; default popup rules
|
||
vc-gutter ; vcs diff in the fringe
|
||
workspaces ; tab emulation, persistence & separate workspaces
|
||
zen ; distraction-free coding or writing
|
||
#+end_src
|
||
|
||
#+name: doom-editor
|
||
#+begin_src emacs-lisp
|
||
(evil +everywhere) ; come to the dark side, we have cookies
|
||
format ; automated prettiness
|
||
#+end_src
|
||
|
||
#+name: doom-emacs
|
||
#+begin_src emacs-lisp
|
||
(dired +icons) ; making dired pretty [functional]
|
||
electric ; smarter, keyword-based electric-indent
|
||
(ibuffer +icons) ; interactive buffer management
|
||
undo ; persistent, smarter undo for your inevitable mistakes
|
||
vc ; version-control and Emacs, sitting in a tree
|
||
#+end_src
|
||
|
||
#+name: doom-term
|
||
#+begin_src emacs-lisp
|
||
vterm ; the best terminal emulation in Emacs
|
||
#+end_src
|
||
|
||
#+name: doom-checkers
|
||
#+begin_src emacs-lisp
|
||
syntax ; tasing you for every semicolon you forget
|
||
(:if (executable-find "aspell") spell) ; tasing you for misspelling mispelling
|
||
(:if (executable-find "languagetool") grammar) ; tasing grammar mistake every you make
|
||
#+end_src
|
||
|
||
#+name: doom-tools
|
||
#+begin_src emacs-lisp
|
||
biblio ; Writes a PhD for you (citation needed)
|
||
(debugger +lsp) ; FIXME stepping through code, to help you add bugs
|
||
(eval +overlay) ; run code, run (also, repls)
|
||
(lookup ; helps you navigate your code and documentation
|
||
+dictionary ; dictionary/thesaurus is nice
|
||
+docsets) ; ...or in Dash docsets locally
|
||
lsp ; Language Server Protocol
|
||
(magit ; a git porcelain for Emacs
|
||
+forge) ; interface with git forges
|
||
pdf ; pdf enhancements
|
||
rgb ; creating color strings
|
||
tree-sitter ; Syntax and Parsing sitting in a tree
|
||
#+end_src
|
||
|
||
#+name: doom-os
|
||
#+begin_src emacs-lisp
|
||
(:if IS-MAC macos) ; improve compatibility with macOS
|
||
#+end_src
|
||
|
||
***** Language support
|
||
We can be rather liberal with enabling support for languages as the associated
|
||
packages/configuration are (usually) only loaded when first opening an
|
||
associated file.
|
||
|
||
#+name: doom-lang
|
||
#+begin_src emacs-lisp
|
||
;;agda ; types of types of types of types...
|
||
;;beancount ; mind the GAAP
|
||
(cc +lsp +tree-sitter) ; C/C++/Obj-C madness
|
||
(clojure +lsp) ; java with a lisp
|
||
;;common-lisp ; if you've seen one lisp, you've seen them all
|
||
;;coq ; proofs-as-programs
|
||
;;crystal ; ruby at the speed of c
|
||
;;csharp ; unity, .NET, and mono shenanigans
|
||
;;data ; config/data formats
|
||
;;(dart +flutter) ; paint ui and not much else
|
||
;;dhall ; JSON with FP sprinkles
|
||
;;elixir ; erlang done right
|
||
;;elm ; care for a cup of TEA?
|
||
emacs-lisp ; drown in parentheses
|
||
;;erlang ; an elegant language for a more civilized age
|
||
;;ess ; emacs speaks statistics
|
||
;;faust ; dsp, but you get to keep your soul
|
||
;;fsharp ; ML stands for Microsoft's Language
|
||
;;fstar ; (dependent) types and (monadic) effects and Z3
|
||
;;gdscript ; the language you waited for
|
||
;;(go +lsp) ; the hipster dialect
|
||
;;(haskell +lsp) ; a language that's lazier than I am
|
||
;;hy ; readability of scheme w/ speed of python
|
||
;;idris ;
|
||
;;json ; At least it ain't XML
|
||
;;(java +lsp) ; the poster child for carpal tunnel syndrome
|
||
;;(javascript +lsp) ; all(hope(abandon(ye(who(enter(here))))))
|
||
;;(julia +lsp) ; Python, R, and MATLAB in a blender
|
||
;;(kotlin +lsp) ; a better, slicker Java(Script)
|
||
(latex ; writing papers in Emacs has never been so fun
|
||
;;+fold ; fold the clutter away nicities
|
||
+latexmk ; modern latex plz
|
||
;;+cdlatex ; quick maths symbols
|
||
+lsp)
|
||
;;lean ; proof that mathematicians need help
|
||
;;factor ; for when scripts are stacked against you
|
||
;;ledger ; an accounting system in Emacs
|
||
(lua +lsp +fennel) ; one-based indices? one-based indices
|
||
(markdown +grip) ; writing docs for people to ignore
|
||
;;nim ; python + lisp at the speed of c
|
||
(nix +tree-sitter) ; I hereby declare "nix geht mehr!"
|
||
;;ocaml ; an objective camel
|
||
(org ; organize your plain life in plain text
|
||
;;+pretty ; yessss my pretties! (nice unicode symbols)
|
||
;;+dragndrop ; drag & drop files/images into org buffers
|
||
;;+hugo ; use Emacs for hugo blogging
|
||
+noter ; enhanced PDF notetaking
|
||
+jupyter ; ipython/jupyter support for babel
|
||
+pandoc ; export-with-pandoc support
|
||
+gnuplot ; who doesn't like pretty pictures
|
||
+pomodoro ; be fruitful with the tomato technique
|
||
+present ; using org-mode for presentations
|
||
+roam2) ; wander around notes
|
||
;;php ; perl's insecure younger brother
|
||
;;plantuml ; diagrams for confusing people more
|
||
;;purescript ; javascript, but functional
|
||
(python ; beautiful is better than ugly
|
||
+lsp
|
||
+pyright
|
||
+tree-sitter
|
||
+conda)
|
||
;;qt ; the 'cutest' gui framework ever
|
||
;;racket ; a DSL for DSLs
|
||
;;raku ; the artist formerly known as perl6
|
||
;;(rust
|
||
;; +lsp
|
||
;; +tree-sitter) ; Fe2O3.unwrap().unwrap().unwrap()
|
||
;;scala ; java, but good
|
||
;;scheme ; a fully conniving family of lisps
|
||
;;(sh +lsp +fish +tree-sitter) ; she sells {ba,z,fi}sh shells on the C xor
|
||
;;sml ; no, the /other/ ML
|
||
;;solidity ; do you need a blockchain? No.
|
||
;;swift ; who asked for emoji variables?
|
||
;;terra ; Earth and Moon in alignment for performance.
|
||
;;(web +lsp) ; the tubes
|
||
;;yaml ; JSON, but readable
|
||
;;zig ; C, but simpler
|
||
#+end_src
|
||
|
||
***** Everything in Emacs
|
||
|
||
While interesting, I prefer using Outlook for email, Reeder for RSS, and Jellyfin for media. I'm not too sure if my Emacs config is stable enough to really replace the others, tbh.
|
||
|
||
#+name: doom-email
|
||
#+begin_src emacs-lisp
|
||
;; (:if (executable-find "mu") (mu4e +org +gmail))
|
||
#+end_src
|
||
|
||
#+name: doom-app
|
||
#+begin_src emacs-lisp
|
||
;;calendar ; A dated approach to timetabling
|
||
;;emms ; Multimedia in Emacs is music to my ears
|
||
;;everywhere ; *leave* Emacs!? You must be joking.
|
||
;; (rss +org) ; emacs as an RSS reader
|
||
#+end_src
|
||
|
||
** Packages
|
||
:PROPERTIES:
|
||
:header-args:emacs-lisp: :tangle no
|
||
:END:
|
||
Unlike most literate configurations I +am lazy+ like to keep all my packages in
|
||
one place
|
||
#+name: packages.el
|
||
#+attr_html: :collapsed t
|
||
#+begin_src emacs-lisp :tangle "packages.el" :noweb no-export :comments no
|
||
;; -*- no-byte-compile: t; -*-
|
||
;;; $DOOMDIR/packages.el
|
||
|
||
;;org
|
||
<<org>>
|
||
|
||
;;latex
|
||
<<latex>>
|
||
|
||
;;looks
|
||
<<looks>>
|
||
|
||
;;emacs additions
|
||
<<emacs>>
|
||
|
||
;;fun
|
||
<<fun>>
|
||
#+end_src
|
||
|
||
***** Org:
|
||
The majority of my work in emacs is done in org mode, even this configuration
|
||
was written in org! It makes sense that the majority of my packages are for
|
||
tweaking org then
|
||
#+name: org
|
||
#+begin_src emacs-lisp
|
||
(package! doct)
|
||
(package! websocket)
|
||
(package! org-appear)
|
||
(package! org-roam-ui)
|
||
(package! org-preview-html)
|
||
#+end_src
|
||
|
||
***** $\LaTeX$:
|
||
When I'm not working in org, I'm probably exporting it to latex. Lets adjust
|
||
that a bit too
|
||
#+name: latex
|
||
#+begin_src emacs-lisp
|
||
(package! aas)
|
||
(package! laas)
|
||
(package! engrave-faces)
|
||
(package! ox-chameleon
|
||
:recipe (:host github :repo "tecosaur/ox-chameleon"))
|
||
#+end_src
|
||
|
||
***** Looks:
|
||
Making emacs look good is first priority, actually working in it is second
|
||
#+name: looks
|
||
#+begin_src emacs-lisp
|
||
(package! focus)
|
||
(package! dimmer)
|
||
(package! minions)
|
||
(package! mini-frame)
|
||
(package! solaire-mode :disable t)
|
||
|
||
;; nano stuff
|
||
(package! nano-theme)
|
||
(package! svg-tag-mode)
|
||
;; (package! nano-modeline)
|
||
#+end_src
|
||
|
||
***** Emacs Tweaks:
|
||
Emacs is missing just a few packages to improve things here and there. Mainly
|
||
- better dictionary support
|
||
- improved modal editing
|
||
- ebook support
|
||
- more colorful docs
|
||
#+name: emacs
|
||
#+begin_src emacs-lisp
|
||
(package! nov)
|
||
(package! lexic)
|
||
(package! info-colors)
|
||
(package! magit-delta :recipe (:host github :repo "dandavison/magit-delta"))
|
||
#+end_src
|
||
|
||
***** Fun:
|
||
We do a little trolling (and reading)
|
||
#+name: fun
|
||
#+begin_src emacs-lisp
|
||
(package! xkcd)
|
||
(package! md4rd)
|
||
(package! smudge)
|
||
(package! elcord)
|
||
(package! monkeytype)
|
||
#+end_src
|
||
|
||
* Basic Configuration
|
||
** Customizations
|
||
Customizations done through the emacs gui should go into their own file, in my doom-dir.
|
||
#+begin_src emacs-lisp
|
||
(setq-default custom-file (expand-file-name ".custom.el" doom-private-dir))
|
||
(when (file-exists-p custom-file)
|
||
(load custom-file))
|
||
#+end_src
|
||
|
||
** Personal information
|
||
Of course we need to tell emacs who I am
|
||
#+begin_src emacs-lisp
|
||
(setq user-full-name "Shaurya Singh"
|
||
user-mail-address "shaunsingh0207@gmail.com")
|
||
#+end_src
|
||
|
||
** Window management
|
||
First, we'll enter the new window
|
||
#+begin_src emacs-lisp
|
||
(setq evil-vsplit-window-right t
|
||
evil-split-window-below t)
|
||
#+end_src
|
||
|
||
Then, we'll pull up a buffer prompt.
|
||
#+begin_src emacs-lisp
|
||
(defadvice! prompt-for-buffer (&rest _)
|
||
:after '(evil-window-split evil-window-vsplit)
|
||
(consult-buffer))
|
||
#+end_src
|
||
|
||
** COMMENT Shell
|
||
Vterm is my terminal emulator of choice. We can tell it to use ligatures, and also tell it to compile automatically
|
||
Vterm clearly wins the terminal war. Also doesn't need much configuration out of
|
||
the box, although the shell integration does.
|
||
|
||
Fixes a weird bug with native-comp
|
||
#+begin_src emacs-lisp
|
||
(setq vterm-always-compile-module t)
|
||
#+end_src
|
||
|
||
If the process exits, kill the =vterm= buffer
|
||
#+begin_src emacs-lisp
|
||
(setq vterm-kill-buffer-on-exit t)
|
||
#+end_src
|
||
|
||
Useful functions for the shell-side integration provided by vterm.
|
||
#+begin_src emacs-lisp
|
||
(after! vterm
|
||
(setf (alist-get "magit-status" vterm-eval-cmds nil nil #'equal)
|
||
'((lambda (path)
|
||
(magit-status path)))))
|
||
#+end_src
|
||
|
||
Use ligatures from within vterm, we do this by redefining the variable where /not/ to show ligatures. On the other hand, in select modes we want to use extra ligatures, so lets enable that.
|
||
#+begin_src emacs-lisp
|
||
(setq +ligatures-in-modes t)
|
||
#+end_src
|
||
|
||
** COMMENT LSP
|
||
I think the LSP is a bit intrusive (especially with inline suggestions), so lets make it behave a bit more
|
||
#+begin_src emacs-lisp
|
||
(after! lsp-mode
|
||
(setq lsp-enable-symbol-highlighting nil))
|
||
|
||
(after! lsp-ui
|
||
(setq lsp-ui-sideline-enable nil ; no more useful than flycheck
|
||
lsp-ui-doc-enable nil)) ; redundant with K
|
||
#+end_src
|
||
|
||
*** Company
|
||
I think company is a bit too quick to recommend some stuff
|
||
#+begin_src emacs-lisp
|
||
(after! company
|
||
(setq company-idle-delay 0.1
|
||
company-selection-wrap-around t
|
||
company-require-match 'never
|
||
company-dabbrev-downcase nil
|
||
company-dabbrev-ignore-case t
|
||
company-dabbrev-other-buffers nil
|
||
company-tooltip-limit 5
|
||
company-tooltip-minimum-width 40)
|
||
(set-company-backend!
|
||
'(text-mode
|
||
markdown-mode
|
||
gfm-mode)
|
||
'(:seperate
|
||
company-files)))
|
||
#+end_src
|
||
|
||
** Better Defaults
|
||
The defaults for emacs aren't so good nowadays. Lets fix that up a bit
|
||
#+begin_src emacs-lisp
|
||
(setq scroll-margin 2
|
||
auto-save-default t
|
||
display-line-numbers-type nil
|
||
delete-by-moving-to-trash t
|
||
truncate-string-ellipsis "…"
|
||
browse-url-browser-function 'xwidget-webkit-browse-url)
|
||
|
||
(fringe-mode 0)
|
||
(global-subword-mode 1)
|
||
#+end_src
|
||
|
||
There's issues with emacs flickering on mac (and sometimes wayland). This should
|
||
fix it
|
||
#+begin_src emacs-lisp
|
||
(add-to-list 'default-frame-alist '(inhibit-double-buffering . t))
|
||
#+end_src
|
||
|
||
Heres some fixes for yabai, we obviously only want that under darwin (macOS) though
|
||
#+begin_src emacs-lisp
|
||
(cond
|
||
((string-equal system-type "darwin")
|
||
(setq frame-resize-pixelwise t
|
||
window-resize-pixelwise t)))
|
||
#+end_src
|
||
|
||
** Evil
|
||
When we do =s/../..= I usually want a global =/g= at the end, so lets make that the default (along with some other tweaks)
|
||
#+begin_src emacs-lisp
|
||
(after! evil
|
||
(setq evil-ex-substitute-global t ; I like my s/../.. to by global by default
|
||
evil-move-cursor-back nil ; Don't move the block cursor when toggling insert mode
|
||
evil-kill-on-visual-paste nil)) ; Don't put overwritten text in the kill ring
|
||
#+end_src
|
||
|
||
Which key shows those extra =evil-= hints, feels redundant
|
||
#+begin_src emacs-lisp
|
||
(setq which-key-allow-multiple-replacements t
|
||
which-key-idle-delay 0.5) ;; I need the help, I really do
|
||
(after! which-key
|
||
(pushnew!
|
||
which-key-replacement-alist
|
||
'(("" . "\\`+?evil[-:]?\\(?:a-\\)?\\(.*\\)") . (nil . " \\1"))
|
||
'(("\\`g s" . "\\`evilem--?motion-\\(.*\\)") . (nil . " \\1"))))
|
||
#+end_src
|
||
|
||
** COMMENT Mu4e
|
||
[[xkcd:1796]]
|
||
|
||
I'm trying out emails in emacs, should be nice. Related, check .mbsyncrc to
|
||
setup your emails first. Usually I'll still prefer the mail app, so lets not go all out
|
||
#+begin_src emacs-lisp
|
||
(after! mu4e
|
||
(setq mu4e-index-cleanup nil
|
||
mu4e-index-lazy-check t
|
||
mu4e-update-interval 300)
|
||
(set-email-account! "shaunsingh0207"
|
||
'((mu4e-sent-folder . "/Sent Mail")
|
||
(mu4e-drafts-folder . "/Drafts")
|
||
(mu4e-trash-folder . "/Trash")
|
||
(mu4e-refile-folder . "/All Mail")
|
||
(smtpmail-smtp-user . "shaunsingh0207@gmail.com"))))
|
||
#+end_src
|
||
|
||
We can also send messages using msmtp
|
||
#+begin_src emacs-lisp
|
||
(after! mu4e
|
||
(setq sendmail-program "msmtp"
|
||
send-mail-function #'smtpmail-send-it
|
||
message-sendmail-f-is-evil t
|
||
message-sendmail-extra-arguments '("--read-envelope-from")
|
||
message-send-mail-function #'message-send-mail-with-sendmail))
|
||
#+end_src
|
||
|
||
** Magit
|
||
Delta is a git diff syntax highlighter written in rust. The author also wrote a package to hook this into the magit diff view (which don’t get any syntax highlighting by default). This requires the delta binary. It’s packaged on some distributions, but most reliably installed through Rust’s package manager cargo.
|
||
#+begin_src emacs-lisp
|
||
(after! magit
|
||
(magit-delta-mode +1))
|
||
#+end_src
|
||
|
||
** COMMENT Monkeytype
|
||
Now that we have some nice keyboard sounds, lets test that keyboard with an elisp clone of Monkeytype!
|
||
Notably here we want to start in insert mode.
|
||
#+begin_src emacs-lisp
|
||
(use-package! monkeytype
|
||
:commands (monkeytype-region monkeytype-buffer monkeytype-region-as-words)
|
||
:config
|
||
(setq monkeytype-directory "~/.config/monkeytype"
|
||
monkeytype-file-name "%a-%d-%b-%Y-%H-%M-%S"
|
||
monkeytype-randomize t
|
||
monkeytype-delete-trailing-whitespace t
|
||
monkeytype-excluded-chars-regexp "[^[:alnum:]']"))
|
||
#+end_src
|
||
|
||
** COMMENT Smudge
|
||
Honestly this probably shouldn't be in emacs, but my music addiction requires it. Those authentication credentials are unique to me, you probably want to change them to your own.
|
||
#+begin_src emacs-lisp
|
||
(use-package! smudge
|
||
:commands global-smudge-remote-mode
|
||
:config
|
||
(setq smudge-transport 'connect
|
||
smudge-oauth2-client-secret "8f5525c076544cd6b25588c868b9b3d7"
|
||
smudge-oauth2-client-id "4b2b46899e604b6884714cd7ca47e0e3")
|
||
(map! :map smudge-mode-map "M-p" #'smudge-command-map))
|
||
#+end_src
|
||
|
||
* Visual configuration
|
||
** COMMENT Dashboard
|
||
Lets clean up the dashboard a bit, and add a cute message, whether that be some corporate BS, an developer excuse, or a fun (useless) fact.
|
||
#+begin_src emacs-lisp
|
||
(setq fancy-splash-image (expand-file-name "misc/splash-images/kaori.png" doom-private-dir) ;; ibm, kaori, fennel
|
||
+doom-dashboard-banner-padding '(0 . 0))
|
||
|
||
(defvar splash-phrase-source-folder
|
||
(expand-file-name "misc/splash-phrases" doom-private-dir)
|
||
"A folder of text files with a fun phrase on each line.")
|
||
|
||
(defvar splash-phrase-sources
|
||
(let* ((files (directory-files splash-phrase-source-folder nil "\\.txt\\'"))
|
||
(sets (delete-dups (mapcar
|
||
(lambda (file)
|
||
(replace-regexp-in-string "\\(?:-[0-9]+-\\w+\\)?\\.txt" "" file))
|
||
files))))
|
||
(mapcar (lambda (sset)
|
||
(cons sset
|
||
(delq nil (mapcar
|
||
(lambda (file)
|
||
(when (string-match-p (regexp-quote sset) file)
|
||
file))
|
||
files))))
|
||
sets))
|
||
"A list of cons giving the phrase set name, and a list of files which contain phrase components.")
|
||
|
||
(defvar splash-phrase-set
|
||
(nth (random (length splash-phrase-sources)) (mapcar #'car splash-phrase-sources))
|
||
"The default phrase set. See `splash-phrase-sources'.")
|
||
|
||
(defun splase-phrase-set-random-set ()
|
||
"Set a new random splash phrase set."
|
||
(interactive)
|
||
(setq splash-phrase-set
|
||
(nth (random (1- (length splash-phrase-sources)))
|
||
(cl-set-difference (mapcar #'car splash-phrase-sources) (list splash-phrase-set))))
|
||
(+doom-dashboard-reload t))
|
||
|
||
(defvar splase-phrase--cache nil)
|
||
|
||
(defun splash-phrase-get-from-file (file)
|
||
"Fetch a random line from FILE."
|
||
(let ((lines (or (cdr (assoc file splase-phrase--cache))
|
||
(cdar (push (cons file
|
||
(with-temp-buffer
|
||
(insert-file-contents (expand-file-name file splash-phrase-source-folder))
|
||
(split-string (string-trim (buffer-string)) "\n")))
|
||
splase-phrase--cache)))))
|
||
(nth (random (length lines)) lines)))
|
||
|
||
(defun splash-phrase (&optional set)
|
||
"Construct a splash phrase from SET. See `splash-phrase-sources'."
|
||
(mapconcat
|
||
#'splash-phrase-get-from-file
|
||
(cdr (assoc (or set splash-phrase-set) splash-phrase-sources))
|
||
" "))
|
||
|
||
(defun doom-dashboard-phrase ()
|
||
"Get a splash phrase, flow it over multiple lines as needed, and make fontify it."
|
||
(mapconcat
|
||
(lambda (line)
|
||
(+doom-dashboard--center
|
||
+doom-dashboard--width
|
||
(with-temp-buffer
|
||
(insert-text-button
|
||
line
|
||
'action
|
||
(lambda (_) (+doom-dashboard-reload t))
|
||
'face 'doom-dashboard-menu-title
|
||
'mouse-face 'doom-dashboard-menu-title
|
||
'help-echo "Random phrase"
|
||
'follow-link t)
|
||
(buffer-string))))
|
||
(split-string
|
||
(with-temp-buffer
|
||
(insert (splash-phrase))
|
||
(setq fill-column (min 70 (/ (* 2 (window-width)) 3)))
|
||
(fill-region (point-min) (point-max))
|
||
(buffer-string))
|
||
"\n")
|
||
"\n"))
|
||
|
||
(defadvice! doom-dashboard-widget-loaded-with-phrase ()
|
||
:override #'doom-dashboard-widget-loaded
|
||
(setq line-spacing 0.2)
|
||
(insert
|
||
"\n\n"
|
||
(propertize
|
||
(+doom-dashboard--center
|
||
+doom-dashboard--width
|
||
(doom-display-benchmark-h 'return))
|
||
'face 'doom-dashboard-loaded)
|
||
"\n"
|
||
(doom-dashboard-phrase)
|
||
"\n"))
|
||
|
||
;; remove useless dashboard info
|
||
(remove-hook '+doom-dashboard-functions #'doom-dashboard-widget-shortmenu)
|
||
(add-hook! '+doom-dashboard-mode-hook (hide-mode-line-mode 1) (hl-line-mode -1))
|
||
(setq-hook! '+doom-dashboard-mode-hook evil-normal-state-cursor (list nil))
|
||
#+end_src
|
||
|
||
** Info Colors
|
||
This makes manual pages nicer to look at by adding variable pitch fontification
|
||
and colouring.
|
||
|
||
To use this we'll just hook it into =Info=.
|
||
#+begin_src emacs-lisp
|
||
(use-package! info-colors
|
||
:commands (info-colors-fontify-node))
|
||
|
||
(add-hook 'Info-selection-hook 'info-colors-fontify-node)
|
||
#+end_src
|
||
|
||
** COMMENT Minibuffer
|
||
Here we set up a cute minibuffer to better fit with our nano-themed niceties. A little more intriqute (and slower) than the default doom setups, but its nice to have.
|
||
#+begin_src emacs-lisp
|
||
(setq minibuffer-prompt-properties '(read-only t
|
||
cursor-intangible t
|
||
face minibuffer-prompt)
|
||
enable-recursive-minibuffers t)
|
||
|
||
(defun my/minibuffer-header ()
|
||
"Minibuffer header"
|
||
(let ((depth (minibuffer-depth)))
|
||
(concat
|
||
(propertize (concat " " (if (> depth 1)
|
||
(format "Minibuffer (%d)" depth)
|
||
"Minibuffer ")
|
||
"\n")
|
||
'face `(:inherit (nano-subtle nano-strong)
|
||
:box (:line-width (1 . 3)
|
||
:color ,(face-background 'nano-subtle)
|
||
:style flat)
|
||
:extend t)))))
|
||
|
||
(defun my/mini-frame-reset (frame)
|
||
"Reset FRAME size and position.
|
||
|
||
Move frame at the top of parent frame and resize it
|
||
horizontally to fit the width of current selected window."
|
||
(interactive)
|
||
(let* ((border (frame-parameter frame 'internal-border-width))
|
||
(height (frame-parameter frame 'height)))
|
||
(with-selected-frame (frame-parent frame)
|
||
(let* ((edges (window-pixel-edges))
|
||
(body-edges (window-body-pixel-edges))
|
||
(top (nth 1 edges))
|
||
(bottom (nth 3 body-edges))
|
||
(left (- (nth 0 edges) (or left-fringe-width 0)))
|
||
(right (+ (nth 2 edges) (or right-fringe-width 0)))
|
||
(width (- right left))
|
||
(y (- top border)))
|
||
(set-frame-width frame width nil t)
|
||
(set-frame-height frame height)
|
||
(set-frame-position frame (- left border) y)))))
|
||
|
||
(defun my/mini-frame-shrink (frame &optional delta)
|
||
"Make the FRAME DELTA lines smaller.
|
||
|
||
If no argument is given, make the frame one line smaller. If
|
||
DELTA is negative, enlarge frame by -DELTA lines."
|
||
(interactive)
|
||
(let ((delta (or delta -1)))
|
||
(when (and (framep frame)
|
||
(frame-live-p frame)
|
||
(frame-visible-p frame))
|
||
(set-frame-parameter frame 'height
|
||
(+ (frame-parameter frame 'height) delta)))))
|
||
|
||
(defun my/minibuffer-setup ()
|
||
"Install a header line in the minibuffer via an overlay (and a hook)"
|
||
(set-window-margins nil 0 0)
|
||
(set-fringe-style '(0 . 0))
|
||
(cursor-intangible-mode t)
|
||
(face-remap-add-relative 'default
|
||
:inherit 'highlight)
|
||
(let* ((overlay (make-overlay (+ (point-min) 0) (+ (point-min) 0)))
|
||
(inhibit-read-only t))
|
||
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
(insert (propertize
|
||
(concat (my/minibuffer-header)
|
||
(propertize "\n" 'face `(:height 0.33))
|
||
(propertize " "))
|
||
'cursor-intangible t
|
||
'read-only t
|
||
'field t
|
||
'rear-nonsticky t
|
||
'front-sticky t)))))
|
||
|
||
|
||
(add-hook 'minibuffer-setup-hook #'my/minibuffer-setup)
|
||
#+end_src
|
||
|
||
** Mini-Frame
|
||
And to go with that, we want to put our minibuffer in a posframe. This can either be placed at the bottom or top of the window, align it with your statusline.
|
||
#+begin_src emacs-lisp
|
||
(use-package! mini-frame
|
||
:hook (after-init . mini-frame-mode)
|
||
:config
|
||
(defcustom my/minibuffer-position 'top
|
||
"Minibuffer position, one of 'top or 'bottom"
|
||
:type '(choice (const :tag "Top" top)
|
||
(const :tag "Bottom" bottom))
|
||
:group 'nano-minibuffer)
|
||
|
||
(defun my/minibuffer--frame-parameters ()
|
||
"Compute minibuffer frame size and position."
|
||
|
||
;; Quite precise computation to align the minibuffer and the
|
||
;; modeline when they are both at top position
|
||
(let* ((edges (window-pixel-edges)) ;; (left top right bottom)
|
||
(body-edges (window-body-pixel-edges)) ;; (left top right bottom)
|
||
(left (nth 0 edges)) ;; Take margins into account
|
||
(top (nth 1 edges)) ;; Drop header line
|
||
(right (nth 2 edges)) ;; Take margins into account
|
||
(bottom (nth 3 body-edges)) ;; Drop header line
|
||
(left (if (eq left-fringe-width 0)
|
||
left
|
||
(- left (frame-parameter nil 'left-fringe))))
|
||
(right (nth 2 edges))
|
||
(right (if (eq right-fringe-width 0)
|
||
right
|
||
(+ right (frame-parameter nil 'right-fringe))))
|
||
(border 1)
|
||
(width (- right left (* 0 border)))
|
||
|
||
;; Window divider mode
|
||
(width (- width (if (and (bound-and-true-p window-divider-mode)
|
||
(or (eq window-divider-default-places 'right-only)
|
||
(eq window-divider-default-places t))
|
||
(window-in-direction 'right (selected-window)))
|
||
window-divider-default-right-width
|
||
0)))
|
||
(y (- top border)))
|
||
|
||
(append `((left-fringe . 0)
|
||
(right-fringe . 0)
|
||
(user-position . t)
|
||
(foreground-color . ,(face-foreground 'highlight nil 'default))
|
||
(background-color . ,(face-background 'highlight nil 'default)))
|
||
(cond ((and (eq my/minibuffer-position 'bottom))
|
||
`((top . -1)
|
||
(left . 0)
|
||
(width . 1.0)
|
||
(child-frame-border-width . 0)
|
||
(internal-border-width . 0)))
|
||
(t
|
||
`((left . ,(- left border))
|
||
(top . ,y)
|
||
(width . (text-pixels . ,width))
|
||
(child-frame-border-width . ,border)
|
||
(internal-border-width . ,border)))))))
|
||
|
||
(set-face-background 'child-frame-border (face-foreground 'nano-faded))
|
||
(setq mini-frame-default-height 3)
|
||
(setq mini-frame-create-lazy t)
|
||
(setq mini-frame-show-parameters 'my/minibuffer--frame-parameters)
|
||
(setq mini-frame-ignore-commands
|
||
'("edebug-eval-expression" debugger-eval-expression))
|
||
(setq mini-frame-internal-border-color (face-foreground 'nano-faded))
|
||
(setq mini-frame-resize-min-height 3)
|
||
(setq mini-frame-resize t)
|
||
|
||
(defun my/mini-frame (&optional height foreground background border)
|
||
"Create a child frame positionned over the header line whose
|
||
width corresponds to the width of the current selected window.
|
||
|
||
The HEIGHT in lines can be specified, as well as the BACKGROUND
|
||
color of the frame. BORDER width (pixels) and color (FOREGROUND)
|
||
can be also specified."
|
||
(interactive)
|
||
(let* ((foreground (or foreground
|
||
(face-foreground 'font-lock-comment-face nil t)))
|
||
(background (or background (face-background 'highlight nil t)))
|
||
(border (or border 1))
|
||
(height (round (* (or height 8) (window-font-height))))
|
||
(edges (window-pixel-edges))
|
||
(body-edges (window-body-pixel-edges))
|
||
(top (nth 1 edges))
|
||
(bottom (nth 3 body-edges))
|
||
(left (- (nth 0 edges) (or left-fringe-width 0)))
|
||
(right (+ (nth 2 edges) (or right-fringe-width 0)))
|
||
(width (- right left))
|
||
|
||
;; Window divider mode
|
||
(width (- width (if (and (bound-and-true-p window-divider-mode)
|
||
(or (eq window-divider-default-places 'right-only)
|
||
(eq window-divider-default-places t))
|
||
(window-in-direction 'right (selected-window)))
|
||
window-divider-default-right-width
|
||
0)))
|
||
(y (- top border))
|
||
(child-frame-border (face-attribute 'child-frame-border :background)))
|
||
(set-face-attribute 'child-frame-border t :background foreground)
|
||
(let ((frame (make-frame
|
||
`((parent-frame . ,(window-frame))
|
||
(delete-before . ,(window-frame))
|
||
(minibuffer . nil)
|
||
(modeline . nil)
|
||
(left . ,(- left border))
|
||
(top . ,y)
|
||
(width . (text-pixels . ,width))
|
||
(height . (text-pixels . ,height))
|
||
;; (height . ,height)
|
||
(child-frame-border-width . ,border)
|
||
(internal-border-width . ,border)
|
||
(background-color . ,background)
|
||
(horizontal-scroll-bars . nil)
|
||
(menu-bar-lines . 0)
|
||
(tool-bar-lines . 0)
|
||
(desktop-dont-save . t)
|
||
(unsplittable . nil)
|
||
(no-other-frame . t)
|
||
(undecorated . t)
|
||
(pixelwise . t)
|
||
(visibility . t)))))
|
||
(set-face-attribute 'child-frame-border t :background child-frame-border)
|
||
frame))))
|
||
#+end_src
|
||
|
||
** Minad Suite
|
||
I feel in love with these packages right away, so much better than icky ivy!
|
||
*** Vertico
|
||
Small tweaks, just some themeing here and there to better fit with our minibuffer changes
|
||
#+begin_src emacs-lisp
|
||
(after! vertico
|
||
;; settings
|
||
(setq vertico-resize nil ; How to resize the Vertico minibuffer window.
|
||
vertico-count 10 ; Maximal number of candidates to show.
|
||
vertico-count-format nil) ; No prefix with number of entries
|
||
|
||
;; looks
|
||
(setq vertico-grid-separator
|
||
#(" | " 2 3 (display (space :width (1))
|
||
face (:background "#ECEFF1")))
|
||
vertico-group-format
|
||
(concat #(" " 0 1 (face vertico-group-title))
|
||
#(" " 0 1 (face vertico-group-separator))
|
||
#(" %s " 0 4 (face vertico-group-title))
|
||
#(" " 0 1 (face vertico-group-separator
|
||
display (space :align-to (- right (-1 . right-margin) (- +1)))))))
|
||
(set-face-attribute 'vertico-group-separator nil
|
||
:strike-through t)
|
||
(set-face-attribute 'vertico-current nil
|
||
:inherit '(nano-strong nano-subtle))
|
||
(set-face-attribute 'completions-first-difference nil
|
||
:inherit '(nano-default))
|
||
|
||
;; minibuffer tweaks
|
||
(defun my/vertico--resize-window (height)
|
||
"Resize active minibuffer window to HEIGHT."
|
||
(setq-local truncate-lines t
|
||
resize-mini-windows 'grow-only
|
||
max-mini-window-height 1.0)
|
||
(unless (frame-root-window-p (active-minibuffer-window))
|
||
(unless vertico-resize
|
||
(setq height (max height vertico-count)))
|
||
(let* ((window-resize-pixelwise t)
|
||
(dp (- (max (cdr (window-text-pixel-size))
|
||
(* (default-line-height) (1+ height)))
|
||
(window-pixel-height))))
|
||
(when (or (and (> dp 0) (/= height 0))
|
||
(and (< dp 0) (eq vertico-resize t)))
|
||
(window-resize nil dp nil nil 'pixelwise)))))
|
||
|
||
(advice-add #'vertico--resize-window :override #'my/vertico--resize-window)
|
||
|
||
;; completion at point
|
||
(setq completion-in-region-function
|
||
(lambda (&rest args)
|
||
(apply (if vertico-mode
|
||
#'consult-completion-in-region
|
||
#'completion--in-region)
|
||
args)))
|
||
(defun minibuffer-format-candidate (orig cand prefix suffix index _start)
|
||
(let ((prefix (if (= vertico--index index)
|
||
" "
|
||
" ")))
|
||
(funcall orig cand prefix suffix index _start)))
|
||
(advice-add #'vertico--format-candidate
|
||
:around #'minibuffer-format-candidate)
|
||
(defun vertico--prompt-selection ()
|
||
"Highlight the prompt"
|
||
|
||
(let ((inhibit-modification-hooks t))
|
||
(set-text-properties (minibuffer-prompt-end) (point-max)
|
||
'(face (nano-strong nano-salient)))))
|
||
(defun minibuffer-vertico-setup ()
|
||
(setq truncate-lines t)
|
||
(setq completion-in-region-function
|
||
(if vertico-mode
|
||
#'consult-completion-in-region
|
||
#'completion--in-region)))
|
||
|
||
(add-hook 'vertico-mode-hook #'minibuffer-vertico-setup)
|
||
(add-hook 'minibuffer-setup-hook #'minibuffer-vertico-setup))
|
||
#+end_src
|
||
|
||
*** Marginalia
|
||
More small tweaks
|
||
#+begin_src emacs-lisp
|
||
(after! marginalia
|
||
(setq marginalia--ellipsis "…" ; Nicer ellipsis
|
||
marginalia-align 'right ; right alignment
|
||
marginalia-align-offset -1)) ; one space on the right
|
||
#+end_src
|
||
|
||
** COMMENT Elcord
|
||
What’s even the point of using Emacs unless you’re constantly telling everyone about it? What we're doing here is replacing the buffer details with something less revealing, then replacing the icon set used via creating a custom discord application. Theoretically this config should work for anyone, but I haven't tested it yet. Thank you cae for the [[file:./misc/lang_icons][icons]]
|
||
#+begin_src emacs-lisp
|
||
(defun shaunsingh/elcord-buffer-details-format ()
|
||
"Return the buffer details string shown on discord."
|
||
(format "Text is a Magical Thing"))
|
||
|
||
(use-package! elcord
|
||
:commands elcord-mode
|
||
:config
|
||
(setq elcord-mode-icon-alist '((dashboard-mode . "elisp-mode_icon")
|
||
(fundamental-mode . "elisp-mode_icon")
|
||
(c-mode . "c-mode_icon")
|
||
(c++-mode . "c_-mode_icon")
|
||
(crystal-mode . "crystal-mode_icon")
|
||
(clojure-mode . "clojure-mode_icon")
|
||
(css-mode . "css-mode_icon")
|
||
(emacs-lisp-mode . "elisp-mode_icon")
|
||
(eshell-mode . "elisp-mode_icon")
|
||
(haskell-mode . "haskell-mode_icon")
|
||
(haxe-mode . "haxe-mode_icon")
|
||
(haskell-interactive-mode . "haskell-mode_icon")
|
||
(js-mode . "javascript-mode_icon")
|
||
(magit-mode . "magit-mode_icon")
|
||
(markdown-mode . "markdown-mode_icon")
|
||
(nixos-mode . "nixos-mode_icon")
|
||
(latex-mode . "latex-mode_icon")
|
||
(text-mode . "elisp-mode_icon")
|
||
(org-mode . "org-mode_icon")
|
||
("^slime-.*" . "lisp-mode_icon")
|
||
("^sly-.*$" . "lisp-mode_icon")
|
||
(typescript-mode . "typescript-mode_icon")
|
||
(writer-mode . "org-mode_icon")
|
||
(term-mode . "x-mode_icon")
|
||
(shell-mode . "x-mode_icon")
|
||
(vterm-mode . "x-mode_icon")))
|
||
(setq elcord-client-id "930927487867834408") ;; You can set your own check elcord's readme
|
||
(setq elcord-quiet t
|
||
elcord-editor-icon "elisp-mode_icon"
|
||
elcord-buffer-details-format-function 'shaunsingh/elcord-buffer-details-format
|
||
elcord-display-buffer-details t
|
||
elcord-display-elapsed nil
|
||
elcord-show-small-icon nil
|
||
elcord-use-major-mode-as-main-icon t
|
||
elcord-refresh-rate 0.25))
|
||
#+end_src
|
||
|
||
** Pixel-scroll
|
||
Default doom scrolling is pretty slow, so lets improve on that with pixel-scrolling. However, =emacs-mac= has its own version of pixel scroll, and so does =emacs29=, so we want to enable this under specific cases
|
||
#+begin_src emacs-lisp
|
||
(if (boundp 'mac-mouse-wheel-smooth-scroll)
|
||
(setq mac-mouse-wheel-smooth-scroll t))
|
||
|
||
(if (> emacs-major-version 28)
|
||
(pixel-scroll-precision-mode))
|
||
#+end_src
|
||
|
||
** Nano
|
||
Some UI tweaks to make emacs comfier
|
||
|
||
Lets start off by just giving the text a little more space to breathe
|
||
#+begin_src emacs-lisp
|
||
(setq-default line-spacing 0.24)
|
||
#+end_src
|
||
|
||
*** Window Padding
|
||
Making things spacier. Add padding around emacs and between splits
|
||
#+begin_src emacs-lisp
|
||
;; Vertical window divider
|
||
(setq-default window-divider-default-right-width 24
|
||
window-divider-default-places 'right-only
|
||
left-margin-width 0
|
||
right-margin-width 0
|
||
window-combination-resize nil) ; Do not resize windows proportionally
|
||
|
||
(window-divider-mode 1)
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
;; Default frame settings
|
||
(setq default-frame-alist '((min-height . 1) '(height . 45)
|
||
(min-width . 1) '(width . 81)
|
||
(vertical-scroll-bars . nil)
|
||
(internal-border-width . 24)
|
||
(left-fringe . 0)
|
||
(right-fringe . 0)
|
||
(tool-bar-lines . 0)
|
||
(menu-bar-lines . 0)))
|
||
|
||
(setq initial-frame-alist default-frame-alist)
|
||
#+end_src
|
||
|
||
*** Colorscheme
|
||
We want to use the nano theme created by the excellent rougier. Heres a small function to change the appearence of the theme based on the system setting. I find myself preferring the light theme, so its disabled but here it is anyways.
|
||
#+begin_src emacs-lisp
|
||
(defun shaunsingh/apply-nano-theme (appearance)
|
||
"Load theme, taking current system APPEARANCE into consideration."
|
||
(mapc #'disable-theme custom-enabled-themes)
|
||
(pcase appearance
|
||
('light (nano-light))
|
||
('dark (nano-dark))))
|
||
#+end_src
|
||
|
||
And now to setup the actual theme. Some extra faces I added because doom modules lookd odd without them
|
||
#+begin_src emacs-lisp
|
||
(use-package nano-theme
|
||
:hook (after-init . nano-light)
|
||
:config
|
||
;; If emacs has been built with system appearance detection
|
||
;; add a hook to change the theme to match the system
|
||
;; (if (boundp 'ns-system-appearance-change-functions)
|
||
;; (add-hook 'ns-system-appearance-change-functions #'shaunsingh/apply-nano-theme))
|
||
;; Now to add some missing faces
|
||
(custom-set-faces
|
||
`(flyspell-incorrect ((t (:underline (:color ,nano-light-salient :style line)))))
|
||
`(flyspell-duplicate ((t (:underline (:color ,nano-light-salient :style line)))))
|
||
|
||
`(git-gutter:modified ((t (:foreground ,nano-light-salient))))
|
||
`(git-gutter-fr:added ((t (:foreground ,nano-light-popout))))
|
||
`(git-gutter-fr:modified ((t (:foreground ,nano-light-salient))))
|
||
|
||
`(lsp-ui-doc-url:added ((t (:background ,nano-light-highlight))))
|
||
`(lsp-ui-doc-background:modified ((t (:background ,nano-light-highlight))))
|
||
|
||
`(vterm-color-red ((t (:foreground ,nano-light-critical))))
|
||
`(vterm-color-blue ((t (:foreground ,nano-light-salient))))
|
||
`(vterm-color-green ((t (:foreground ,nano-light-popout))))
|
||
`(vterm-color-yellow ((t (:foreground ,nano-light-popout))))
|
||
`(vterm-color-magenta ((t (:foreground ,nano-light-salient))))
|
||
|
||
`(scroll-bar ((t (:background ,nano-light-background))))
|
||
`(child-frame-border ((t (:foreground ,nano-light-faded))))
|
||
|
||
`(avy-lead-face-1 ((t (:foreground ,nano-light-subtle))))
|
||
`(avy-lead-face ((t (:foreground ,nano-light-popout :weight bold))))
|
||
`(avy-lead-face-0 ((t (:foreground ,nano-light-salient :weight bold))))))
|
||
#+end_src
|
||
|
||
Originally I was going to use nano-modeline, but I prefer the default one anyways. We can use the excellent minions package to clean it up though.
|
||
#+begin_src emacs-lisp
|
||
;; (use-package! nano-modeline
|
||
;; :hook (after-init . nano-modeline-mode)
|
||
;; :config
|
||
;; (setq nano-modeline-prefix 'status
|
||
;; nano-modeline-prefix-padding 1
|
||
;; nano-modeline-position 'bottom))
|
||
|
||
(use-package! minions
|
||
:hook (after-init . minions-mode))
|
||
|
||
;; Add a zero-width tall character to add padding to modeline
|
||
(setq-default mode-line-format
|
||
(cons (propertize "\u200b" 'display '((raise -0.35) (height 1.4))) mode-line-format))
|
||
#+end_src
|
||
|
||
** COMMENT SVG-tag-mode
|
||
Replaced org-modern with this, a little heavier on emacs but looks better (imo).
|
||
#+begin_src emacs-lisp
|
||
(use-package svg-tag-mode
|
||
:commands svg-tag-mode
|
||
:config
|
||
(defconst date-re "[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}")
|
||
(defconst time-re "[0-9]\\{2\\}:[0-9]\\{2\\}")
|
||
(defconst day-re "[A-Za-z]\\{3\\}")
|
||
(defconst day-time-re (format "\\(%s\\)? ?\\(%s\\)?" day-re time-re))
|
||
|
||
(defun svg-progress-percent (value)
|
||
(svg-image (svg-lib-concat
|
||
(svg-lib-progress-bar (/ (string-to-number value) 100.0)
|
||
nil :margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
|
||
(svg-lib-tag (concat value "%")
|
||
nil :stroke 0 :margin 0)) :ascent 'center))
|
||
|
||
(defun svg-progress-count (value)
|
||
(let* ((seq (mapcar #'string-to-number (split-string value "/")))
|
||
(count (float (car seq)))
|
||
(total (float (cadr seq))))
|
||
(svg-image (svg-lib-concat
|
||
(svg-lib-progress-bar (/ count total) nil
|
||
:margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
|
||
(svg-lib-tag value nil
|
||
:stroke 0 :margin 0)) :ascent 'center)))
|
||
|
||
(setq svg-tag-tags
|
||
`(
|
||
;; Org tags
|
||
(":\\([A-Za-z0-9]+\\)" . ((lambda (tag) (svg-tag-make tag))))
|
||
(":\\([A-Za-z0-9]+[ \-]\\)" . ((lambda (tag) tag)))
|
||
|
||
;; Task priority
|
||
("\\[#[A-Z]\\]" . ( (lambda (tag)
|
||
(svg-tag-make tag :face 'org-priority
|
||
:beg 2 :end -1 :margin 0))))
|
||
|
||
;; Progress
|
||
("\\(\\[[0-9]\\{1,3\\}%\\]\\)" . ((lambda (tag)
|
||
(svg-progress-percent (substring tag 1 -2)))))
|
||
("\\(\\[[0-9]+/[0-9]+\\]\\)" . ((lambda (tag)
|
||
(svg-progress-count (substring tag 1 -1)))))
|
||
|
||
;; TODO / DONE
|
||
("TODO" . ((lambda (tag) (svg-tag-make "TODO" :face 'org-todo :inverse t :margin 0))))
|
||
("DONE" . ((lambda (tag) (svg-tag-make "DONE" :face 'org-done :margin 0))))
|
||
|
||
|
||
;; Citation of the form [cite:@Knuth:1984]
|
||
("\\(\\[cite:@[A-Za-z]+:\\)" . ((lambda (tag)
|
||
(svg-tag-make tag
|
||
:inverse t
|
||
:beg 7 :end -1
|
||
:crop-right t))))
|
||
("\\[cite:@[A-Za-z]+:\\([0-9]+\\]\\)" . ((lambda (tag)
|
||
(svg-tag-make tag
|
||
:end -1
|
||
:crop-left t))))
|
||
|
||
|
||
;; Active date (with or without day name, with or without time)
|
||
(,(format "\\(<%s>\\)" date-re) .
|
||
((lambda (tag)
|
||
(svg-tag-make tag :beg 1 :end -1 :margin 0))))
|
||
(,(format "\\(<%s \\)%s>" date-re day-time-re) .
|
||
((lambda (tag)
|
||
(svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0))))
|
||
(,(format "<%s \\(%s>\\)" date-re day-time-re) .
|
||
((lambda (tag)
|
||
(svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0))))
|
||
|
||
;; Inactive date (with or without day name, with or without time)
|
||
(,(format "\\(\\[%s\\]\\)" date-re) .
|
||
((lambda (tag)
|
||
(svg-tag-make tag :beg 1 :end -1 :margin 0 :face 'org-date))))
|
||
(,(format "\\(\\[%s \\)%s\\]" date-re day-time-re) .
|
||
((lambda (tag)
|
||
(svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0 :face 'org-date))))
|
||
(,(format "\\[%s \\(%s\\]\\)" date-re day-time-re) .
|
||
((lambda (tag)
|
||
(svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0 :face 'org-date)))))))
|
||
#+end_src
|
||
|
||
** Dimming
|
||
#+begin_src emacs-lisp
|
||
;; Dim inactive windows
|
||
(use-package! dimmer
|
||
:hook (after-init . dimmer-mode)
|
||
:config
|
||
(setq dimmer-fraction 0.5
|
||
dimmer-adjustment-mode :foreground
|
||
dimmer-use-colorspace :rgb
|
||
dimmer-watch-frame-focus-events nil)
|
||
(dimmer-configure-which-key)
|
||
(dimmer-configure-magit)
|
||
(dimmer-configure-posframe))
|
||
#+end_src
|
||
|
||
Similar to that, I want to dim surrounding text using the focus package
|
||
#+begin_src emacs-lisp
|
||
(defun add-list-to-list (dst src)
|
||
"Similar to `add-to-list', but accepts a list as 2nd argument"
|
||
(set dst
|
||
(append (eval dst) src)))
|
||
|
||
(use-package! focus
|
||
:commands focus-mode
|
||
:config
|
||
;; add whatever lsp servers you use to this list
|
||
(add-list-to-list 'focus-mode-to-thing
|
||
'((lua-mode . lsp-folding-range)
|
||
(rust-mode . lsp-folding-range)
|
||
(latex-mode . lsp-folding-range)
|
||
(python-mode . lsp-folding-range))))
|
||
#+end_src
|
||
|
||
** Writeroom
|
||
For starters, I think Doom is a bit over-zealous when zooming in
|
||
#+begin_src emacs-lisp
|
||
(setq +zen-text-scale 0.8)
|
||
#+end_src
|
||
|
||
** COMMENT RSS
|
||
RSS is a nice simple way of getting my news. Lets set that up
|
||
#+begin_src emacs-lisp
|
||
(map! :map elfeed-search-mode-map
|
||
:after elfeed-search
|
||
[remap kill-this-buffer] "q"
|
||
[remap kill-buffer] "q"
|
||
:n doom-leader-key nil
|
||
:n "q" #'+rss/quit
|
||
:n "e" #'elfeed-update
|
||
:n "r" #'elfeed-search-untag-all-unread
|
||
:n "u" #'elfeed-search-tag-all-unread
|
||
:n "s" #'elfeed-search-live-filter
|
||
:n "RET" #'elfeed-search-show-entry
|
||
:n "p" #'elfeed-show-pdf
|
||
:n "+" #'elfeed-search-tag-all
|
||
:n "-" #'elfeed-search-untag-all
|
||
:n "S" #'elfeed-search-set-filter
|
||
:n "b" #'elfeed-search-browse-url
|
||
:n "y" #'elfeed-search-yank)
|
||
(map! :map elfeed-show-mode-map
|
||
:after elfeed-show
|
||
[remap kill-this-buffer] "q"
|
||
[remap kill-buffer] "q"
|
||
:n doom-leader-key nil
|
||
:nm "q" #'+rss/delete-pane
|
||
:nm "o" #'ace-link-elfeed
|
||
:nm "RET" #'org-ref-elfeed-add
|
||
:nm "n" #'elfeed-show-next
|
||
:nm "N" #'elfeed-show-prev
|
||
:nm "p" #'elfeed-show-pdf
|
||
:nm "+" #'elfeed-show-tag
|
||
:nm "-" #'elfeed-show-untag
|
||
:nm "s" #'elfeed-show-new-live-search
|
||
:nm "y" #'elfeed-show-yank)
|
||
|
||
(after! elfeed-search
|
||
(set-evil-initial-state! 'elfeed-search-mode 'normal))
|
||
(after! elfeed-show-mode
|
||
(set-evil-initial-state! 'elfeed-show-mode 'normal))
|
||
|
||
(after! evil-snipe
|
||
(push 'elfeed-show-mode evil-snipe-disabled-modes)
|
||
(push 'elfeed-search-mode evil-snipe-disabled-modes))
|
||
|
||
(after! elfeed
|
||
(elfeed-org)
|
||
(use-package! elfeed-link)
|
||
(setq rmh-elfeed-org-files '("~/org/elfeed.org"))
|
||
|
||
(setq elfeed-search-filter "@1-week-ago +unread"
|
||
elfeed-search-print-entry-function '+rss/elfeed-search-print-entry
|
||
elfeed-search-title-min-width 80
|
||
elfeed-show-entry-switch #'pop-to-buffer
|
||
elfeed-show-entry-delete #'+rss/delete-pane
|
||
elfeed-show-refresh-function #'+rss/elfeed-show-refresh--better-style
|
||
shr-max-image-proportion 0.6)
|
||
|
||
(add-hook! 'elfeed-show-mode-hook (hide-mode-line-mode 1))
|
||
(add-hook! 'elfeed-search-update-hook #'hide-mode-line-mode)
|
||
|
||
(defface elfeed-show-title-face '((t (:weight ultrabold :slant italic :height 1.5)))
|
||
"title face in elfeed show buffer"
|
||
:group 'elfeed)
|
||
(defface elfeed-show-author-face `((t (:weight light)))
|
||
"title face in elfeed show buffer"
|
||
:group 'elfeed)
|
||
(set-face-attribute 'elfeed-search-title-face nil
|
||
:foreground 'nil
|
||
:weight 'light)
|
||
|
||
(defadvice! +rss-elfeed-wrap-h-nicer ()
|
||
"Enhances an elfeed entry's readability by wrapping it to a width of
|
||
`fill-column' and centering it with `visual-fill-column-mode'."
|
||
:override #'+rss-elfeed-wrap-h
|
||
(setq-local truncate-lines nil
|
||
shr-width 120
|
||
visual-fill-column-center-text t
|
||
default-text-properties '(line-height 1.1))
|
||
(let ((inhibit-read-only t)
|
||
(inhibit-modification-hooks t))
|
||
(visual-fill-column-mode)
|
||
;; (setq-local shr-current-font '(:family "Merriweather" :height 1.2))
|
||
(set-buffer-modified-p nil)))
|
||
|
||
(defun +rss/elfeed-search-print-entry (entry)
|
||
"Print ENTRY to the buffer."
|
||
(let* ((elfeed-goodies/tag-column-width 40)
|
||
(elfeed-goodies/feed-source-column-width 30)
|
||
(title (or (elfeed-meta entry :title) (elfeed-entry-title entry) ""))
|
||
(title-faces (elfeed-search--faces (elfeed-entry-tags entry)))
|
||
(feed (elfeed-entry-feed entry))
|
||
(feed-title
|
||
(when feed
|
||
(or (elfeed-meta feed :title) (elfeed-feed-title feed))))
|
||
(tags (mapcar #'symbol-name (elfeed-entry-tags entry)))
|
||
(tags-str (concat (mapconcat 'identity tags ",")))
|
||
(title-width (- (window-width) elfeed-goodies/feed-source-column-width
|
||
elfeed-goodies/tag-column-width 4))
|
||
|
||
(tag-column (elfeed-format-column
|
||
tags-str (elfeed-clamp (length tags-str)
|
||
elfeed-goodies/tag-column-width
|
||
elfeed-goodies/tag-column-width)
|
||
:left))
|
||
(feed-column (elfeed-format-column
|
||
feed-title (elfeed-clamp elfeed-goodies/feed-source-column-width
|
||
elfeed-goodies/feed-source-column-width
|
||
elfeed-goodies/feed-source-column-width)
|
||
:left)))
|
||
|
||
(insert (propertize feed-column 'face 'elfeed-search-feed-face) " ")
|
||
(insert (propertize tag-column 'face 'elfeed-search-tag-face) " ")
|
||
(insert (propertize title 'face title-faces 'kbd-help title))))
|
||
|
||
(defun +rss/elfeed-show-refresh--better-style ()
|
||
"Update the buffer to match the selected entry, using a mail-style."
|
||
(interactive)
|
||
(let* ((inhibit-read-only t)
|
||
(title (elfeed-entry-title elfeed-show-entry))
|
||
(date (seconds-to-time (elfeed-entry-date elfeed-show-entry)))
|
||
(author (elfeed-meta elfeed-show-entry :author))
|
||
(link (elfeed-entry-link elfeed-show-entry))
|
||
(tags (elfeed-entry-tags elfeed-show-entry))
|
||
(tagsstr (mapconcat #'symbol-name tags ", "))
|
||
(nicedate (format-time-string "%a, %e %b %Y %T %Z" date))
|
||
(content (elfeed-deref (elfeed-entry-content elfeed-show-entry)))
|
||
(type (elfeed-entry-content-type elfeed-show-entry))
|
||
(feed (elfeed-entry-feed elfeed-show-entry))
|
||
(feed-title (elfeed-feed-title feed))
|
||
(base (and feed (elfeed-compute-base (elfeed-feed-url feed)))))
|
||
(erase-buffer)
|
||
(insert "\n")
|
||
(insert (format "%s\n\n" (propertize title 'face 'elfeed-show-title-face)))
|
||
(insert (format "%s\t" (propertize feed-title 'face 'elfeed-search-feed-face)))
|
||
(when (and author elfeed-show-entry-author)
|
||
(insert (format "%s\n" (propertize author 'face 'elfeed-show-author-face))))
|
||
(insert (format "%s\n\n" (propertize nicedate 'face 'elfeed-log-date-face)))
|
||
(when tags
|
||
(insert (format "%s\n"
|
||
(propertize tagsstr 'face 'elfeed-search-tag-face))))
|
||
;; (insert (propertize "Link: " 'face 'message-header-name))
|
||
;; (elfeed-insert-link link link)
|
||
;; (insert "\n")
|
||
(cl-loop for enclosure in (elfeed-entry-enclosures elfeed-show-entry)
|
||
do (insert (propertize "Enclosure: " 'face 'message-header-name))
|
||
do (elfeed-insert-link (car enclosure))
|
||
do (insert "\n"))
|
||
(insert "\n")
|
||
(if content
|
||
(if (eq type 'html)
|
||
(elfeed-insert-html content base)
|
||
(insert content))
|
||
(insert (propertize "(empty)\n" 'face 'italic)))
|
||
(goto-char (point-min)))))
|
||
|
||
(after! elfeed-show
|
||
(require 'url)
|
||
|
||
(defvar elfeed-pdf-dir
|
||
(expand-file-name "pdfs/"
|
||
(file-name-directory (directory-file-name elfeed-enclosure-default-dir))))
|
||
|
||
(defvar elfeed-link-pdfs
|
||
'(("https://www.jstatsoft.org/index.php/jss/article/view/v0\\([^/]+\\)" . "https://www.jstatsoft.org/index.php/jss/article/view/v0\\1/v\\1.pdf")
|
||
("http://arxiv.org/abs/\\([^/]+\\)" . "https://arxiv.org/pdf/\\1.pdf"))
|
||
"List of alists of the form (REGEX-FOR-LINK . FORM-FOR-PDF)")
|
||
|
||
(defun elfeed-show-pdf (entry)
|
||
(interactive
|
||
(list (or elfeed-show-entry (elfeed-search-selected :ignore-region))))
|
||
(let ((link (elfeed-entry-link entry))
|
||
(feed-name (plist-get (elfeed-feed-meta (elfeed-entry-feed entry)) :title))
|
||
(title (elfeed-entry-title entry))
|
||
(file-view-function
|
||
(lambda (f)
|
||
(when elfeed-show-entry
|
||
(elfeed-kill-buffer))
|
||
(pop-to-buffer (find-file-noselect f))))
|
||
pdf)
|
||
|
||
(let ((file (expand-file-name
|
||
(concat (subst-char-in-string ?/ ?, title) ".pdf")
|
||
(expand-file-name (subst-char-in-string ?/ ?, feed-name)
|
||
elfeed-pdf-dir))))
|
||
(if (file-exists-p file)
|
||
(funcall file-view-function file)
|
||
(dolist (link-pdf elfeed-link-pdfs)
|
||
(when (and (string-match-p (car link-pdf) link)
|
||
(not pdf))
|
||
(setq pdf (replace-regexp-in-string (car link-pdf) (cdr link-pdf) link))))
|
||
(if (not pdf)
|
||
(message "No associated PDF for entry")
|
||
(message "Fetching %s" pdf)
|
||
(unless (file-exists-p (file-name-directory file))
|
||
(make-directory (file-name-directory file) t))
|
||
(url-copy-file pdf file)
|
||
(funcall file-view-function file)))))))
|
||
#+end_src
|
||
|
||
** COMMENT Ebooks
|
||
[[xkcd:548]]
|
||
|
||
To actually read the ebooks we use =nov=.
|
||
#+begin_src emacs-lisp
|
||
(use-package! nov
|
||
:mode ("\\.epub\\'" . nov-mode)
|
||
:config
|
||
(map! :map nov-mode-map
|
||
:n "RET" #'nov-scroll-up)
|
||
|
||
(advice-add 'nov-render-title :override #'ignore)
|
||
(defun +nov-mode-setup ()
|
||
(face-remap-add-relative 'default :height 1.3)
|
||
(setq-local next-screen-context-lines 4
|
||
shr-use-colors nil)
|
||
(require 'visual-fill-column nil t)
|
||
(setq-local visual-fill-column-center-text t
|
||
visual-fill-column-width 81
|
||
nov-text-width 80)
|
||
(visual-fill-column-mode 1)
|
||
(add-to-list '+lookup-definition-functions #'+lookup/dictionary-definition)
|
||
(add-hook 'nov-mode-hook #'+nov-mode-setup)))
|
||
#+end_src
|
||
|
||
|
||
|
||
* Org
|
||
** Org-Mode
|
||
I really like org mode, I've given some thought to why, and below is the result.
|
||
#+attr_latex: :align *{8}{p{0.105\linewidth}} :font \small
|
||
| Format | Fine-grained control | Initial ease of use | Syntax simplicity | Editor Support | Integrations | Ease-of-referencing | Versatility |
|
||
|-------------------+----------------------+---------------------+-------------------+----------------+--------------+---------------------+-------------|
|
||
| Word | 2 | 4 | 4 | 2 | 3 | 2 | 2 |
|
||
| LaTeX | 4 | 1 | 1 | 3 | 2 | 4 | 3 |
|
||
| Org Mode | 4 | 2 | 3.5 | 1 | 4 | 4 | 4 |
|
||
| Markdown | 1 | 3 | 3 | 4 | 3 | 3 | 1 |
|
||
| Markdown + Pandoc | 2.5 | 2.5 | 2.5 | 3 | 3 | 3 | 2 |
|
||
|
||
|
||
Beyond the elegance in the markup language, tremendously rich integrations with
|
||
Emacs allow for some fantastic [[https://orgmode.org/features.html][features]], such as what seems to be the best
|
||
support for [[https://en.wikipedia.org/wiki/Literate_programming][literate programming]] of any currently available technology.
|
||
|
||
I prefer /org as my directory. Lets change some other defaults too
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-directory "~/org" ; let's put files here
|
||
org-ellipsis " " ; cute icon for folded org blocks
|
||
org-list-allow-alphabetical t ; have a. A. a) A) list bullets
|
||
org-use-property-inheritance t ; it's convenient to have properties inherited
|
||
org-catch-invisible-edits 'smart ; try not to accidently do weird stuff in invisible regions
|
||
org-log-done 'time ; having the time a item is done sounds convenient
|
||
org-roam-directory "~/org/roam/")) ; same thing, for roam
|
||
#+end_src
|
||
|
||
And some extra fontification doesn't hurt
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-src-fontify-natively t
|
||
org-fontify-whole-heading-line t
|
||
org-inline-src-prettify-results '("⟨" . "⟩")
|
||
org-fontify-done-headline t
|
||
org-fontify-quote-and-verse-blocks t))
|
||
#+end_src
|
||
|
||
I want to slightly change the default args for babel
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-babel-default-header-args
|
||
'((:session . "none")
|
||
(:results . "replace")
|
||
(:exports . "code")
|
||
(:cache . "no")
|
||
(:noweb . "no")
|
||
(:hlines . "no")
|
||
(:tangle . "no")
|
||
(:comments . "link"))))
|
||
#+end_src
|
||
|
||
I also want to change the order of bullets
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+") ("1." . "a."))))
|
||
#+end_src
|
||
|
||
And the default dashes and =+= signs just don't cut it anymore. Lets make them fancy bullets instead
|
||
#+begin_src emacs-lisp
|
||
(font-lock-add-keywords 'org-mode
|
||
'(("^ *\\([-]\\) "
|
||
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "•"))))))
|
||
(font-lock-add-keywords 'org-mode
|
||
'(("^ *\\([+]\\) "
|
||
(0 (prog1 () (compose-region (match-beginning 1) (match-end 1) "◦"))))))
|
||
#+end_src
|
||
|
||
The =[[yt:...]]= links preview nicely, but don’t export nicely. Thankfully, we can fix that.
|
||
#+begin_src emacs-lisp
|
||
(after! ox
|
||
(org-link-set-parameters "yt" :export #'+org-export-yt)
|
||
(defun +org-export-yt (path desc backend _com)
|
||
(cond ((org-export-derived-backend-p backend 'html)
|
||
(format "<iframe width='440' \
|
||
height='335' \
|
||
src='https://www.youtube.com/embed/%s' \
|
||
frameborder='0' \
|
||
allowfullscreen>%s</iframe>" path (or "" desc)))
|
||
((org-export-derived-backend-p backend 'latex)
|
||
(format "\\href{https://youtu.be/%s}{%s}" path (or desc "youtube")))
|
||
(t (format "https://youtu.be/%s" path)))))
|
||
#+end_src
|
||
|
||
*** HTML export
|
||
Inspired by Tecosaur's amazing org-css, I wanted to make my own, but with fewer features and slightly cleaner overall.
|
||
#+begin_src emacs-lisp
|
||
(defun org-inline-css-hook (exporter)
|
||
"Insert custom inline css"
|
||
(when (eq exporter 'html)
|
||
(let* ((dir (ignore-errors (file-name-directory (buffer-file-name))))
|
||
(path (concat dir "style.css"))
|
||
(homestyle (or (null dir) (null (file-exists-p path))))
|
||
(final (if homestyle (expand-file-name "misc/org-css/style.css" doom-private-dir) path)))
|
||
(setq org-html-head-include-default-style nil)
|
||
(setq org-html-head (concat
|
||
"<style type=\"text/css\">\n"
|
||
"<!--/*--><![CDATA[/*><!--*/\n"
|
||
(with-temp-buffer
|
||
(insert-file-contents final)
|
||
(buffer-string))
|
||
"/*]]>*/-->\n"
|
||
"</style>\n")))))
|
||
|
||
(defun org-inline-js-hook (exporter)
|
||
"Insert custom inline css"
|
||
(when (eq exporter 'html)
|
||
(let* ((dir (ignore-errors (file-name-directory (buffer-file-name))))
|
||
(path (concat dir "style.js"))
|
||
(homestyle (or (null dir) (null (file-exists-p path))))
|
||
(final (if homestyle (expand-file-name "misc/org-css/style.js" doom-private-dir) path)))
|
||
(setq org-html-head-include-default-style nil)
|
||
(setq org-html-head (concat
|
||
"<script type=\"text/javascript\">\n"
|
||
"<!--/*--><![CDATA[/*><!--*/\n"
|
||
(with-temp-buffer
|
||
(insert-file-contents final)
|
||
(buffer-string))
|
||
"/*]]>*/-->\n"
|
||
"</script>\n")))))
|
||
|
||
(defun org-inline-html-hook (exporter)
|
||
"Insert custom inline css"
|
||
(when (eq exporter 'html)
|
||
(let* ((dir (ignore-errors (file-name-directory (buffer-file-name))))
|
||
(path (concat dir "style.html"))
|
||
(homestyle (or (null dir) (null (file-exists-p path))))
|
||
(final (if homestyle (expand-file-name "misc/org-css/style.html" doom-private-dir) path)))
|
||
(setq org-html-head-include-default-style nil)
|
||
(setq org-html-head (concat
|
||
(with-temp-buffer
|
||
(insert-file-contents final)
|
||
(buffer-string))
|
||
"\n")))))
|
||
|
||
(add-hook 'org-export-before-processing-hook 'org-inline-css-hook)
|
||
(add-hook 'org-export-before-processing-hook 'org-inline-js-hook)
|
||
(add-hook 'org-export-before-processing-hook 'org-inline-html-hook)
|
||
#+end_src
|
||
|
||
If MathJax is used, we want to use version 3 instead of the default version 2.
|
||
Looking at a [[https://www.intmath.com/cg5/katex-mathjax-comparison.php][comparison]] we seem to find that it is ~5 times as fast, uses a
|
||
single file instead of multiple, but seems to be a bit bigger unfortunately.
|
||
Thankfully this can be mitigated my adding the ~async~ attribute to defer loading.
|
||
|
||
#+begin_src emacs-lisp
|
||
(after! ox-html
|
||
(setq org-html-mathjax-options
|
||
'((path "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" )
|
||
(scale "1")
|
||
(autonumber "ams")
|
||
(multlinewidth "85%")
|
||
(tagindent ".8em")
|
||
(tagside "right")))
|
||
|
||
(setq org-html-mathjax-template
|
||
"<script>
|
||
MathJax = {
|
||
chtml: {
|
||
scale: %SCALE
|
||
},
|
||
svg: {
|
||
scale: %SCALE,
|
||
fontCache: \"global\"
|
||
},
|
||
tex: {
|
||
tags: \"%AUTONUMBER\",
|
||
multlineWidth: \"%MULTLINEWIDTH\",
|
||
tagSide: \"%TAGSIDE\",
|
||
tagIndent: \"%TAGINDENT\"
|
||
}
|
||
};
|
||
</script>
|
||
<script id=\"MathJax-script\" async
|
||
src=\"%PATH\"></script>"))
|
||
#+end_src
|
||
|
||
And now to preview that export live
|
||
#+begin_src emacs-lisp
|
||
(use-package! org-preview-html
|
||
:commands org-preview-html-mode
|
||
:config
|
||
(setq org-preview-html-refresh-configuration 'save
|
||
org-preview-html-viewer 'xwidget))
|
||
#+end_src
|
||
|
||
I like to preview images inline too
|
||
#+begin_src emacs-lisp
|
||
(setq org-startup-with-inline-images t)
|
||
#+end_src
|
||
|
||
** COMMENT Org-Roam
|
||
Lets set up =org-roam-ui=
|
||
#+begin_src emacs-lisp
|
||
(use-package! websocket
|
||
:after org-roam)
|
||
|
||
(use-package! org-roam-ui
|
||
:after org-roam
|
||
:commands org-roam-ui-open
|
||
:config
|
||
(setq org-roam-ui-sync-theme t
|
||
org-roam-ui-follow t
|
||
org-roam-ui-update-on-save t
|
||
org-roam-ui-open-on-start t))
|
||
#+end_src
|
||
|
||
Now, I want to replace the org-roam buffer with org-roam-ui, to do that, we need
|
||
to disable the regular buffer
|
||
#+begin_src emacs-lisp
|
||
(after! org-roam
|
||
(setq +org-roam-open-buffer-on-find-file nil))
|
||
#+end_src
|
||
|
||
** COMMENT Org-Agenda
|
||
Set the directory
|
||
#+begin_src emacs-lisp
|
||
(after! org-agenda
|
||
(setq org-agenda-files (list "~/org/school.org"
|
||
"~/org/todo.org"))
|
||
(setq org-agenda-window-setup 'current-window
|
||
org-agenda-restore-windows-after-quit t
|
||
org-agenda-show-all-dates nil
|
||
org-agenda-time-in-grid t
|
||
org-agenda-show-current-time-in-grid t
|
||
org-agenda-start-on-weekday 1
|
||
org-agenda-span 7
|
||
org-agenda-tags-column 0
|
||
org-agenda-block-separator nil
|
||
org-agenda-category-icon-alist nil
|
||
org-agenda-sticky t)
|
||
(setq org-agenda-prefix-format
|
||
'((agenda . "%i %?-12t%s")
|
||
(todo . "%i")
|
||
(tags . "%i")
|
||
(search . "%i")))
|
||
(setq org-agenda-sorting-strategy
|
||
'((agenda deadline-down scheduled-down todo-state-up time-up
|
||
habit-down priority-down category-keep)
|
||
(todo priority-down category-keep)
|
||
(tags timestamp-up priority-down category-keep)
|
||
(search category-keep))))
|
||
#+end_src
|
||
|
||
** COMMENT Font Display
|
||
It seems reasonable to have deadlines in the error face when they're passed.
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-agenda-deadline-faces
|
||
'((1.0 . error)
|
||
(1.0 . org-warning)
|
||
(0.5 . org-upcoming-deadline)
|
||
(0.0 . org-upcoming-distant-deadline))))
|
||
#+end_src
|
||
|
||
And lets conceal *those* /syntax/ +markers+.
|
||
#+begin_src emacs-lisp
|
||
(use-package! org-appear
|
||
:after org
|
||
:hook (org-mode . org-appear-mode)
|
||
:config
|
||
(setq org-appear-autoemphasis t
|
||
org-appear-autolinks t
|
||
org-appear-autosubmarkers t))
|
||
#+end_src
|
||
|
||
*** (sub|super)script characters
|
||
Annoying having to gate these, so let's fix that
|
||
#+begin_src emacs-lisp
|
||
(setq org-export-with-sub-superscripts '{})
|
||
#+end_src
|
||
|
||
*** 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).
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-latex-text-markup-alist
|
||
'((bold . "\\textbf{%s}")
|
||
(code . protectedtexttt)
|
||
(italic . "\\emph{%s}")
|
||
(strike-through . "\\sout{%s}")
|
||
(underline . "\\uline{%s}")
|
||
(verbatim . verb))))
|
||
#+end_src
|
||
|
||
** COMMENT XKCD
|
||
[[xkcd:446]]
|
||
|
||
#+begin_quote
|
||
Relevent XKCD:
|
||
#+end_quote
|
||
|
||
I link to xkcd's so much that its better to just have a configuration for them
|
||
We want to set this up so it loads nicely in org.
|
||
#+begin_src emacs-lisp
|
||
(use-package! xkcd
|
||
:commands (xkcd-get-json
|
||
xkcd-download xkcd-get
|
||
;; now for funcs from my extension of this pkg
|
||
+xkcd-find-and-copy +xkcd-find-and-view
|
||
+xkcd-fetch-info +xkcd-select)
|
||
:config
|
||
(setq xkcd-cache-dir (expand-file-name "xkcd/" doom-cache-dir)
|
||
xkcd-cache-latest (concat xkcd-cache-dir "latest"))
|
||
(unless (file-exists-p xkcd-cache-dir)
|
||
(make-directory xkcd-cache-dir))
|
||
:general (:states 'normal
|
||
:keymaps 'xkcd-mode-map
|
||
"<right>" #'xkcd-next
|
||
"n" #'xkcd-next
|
||
"<left>" #'xkcd-prev
|
||
"N" #'xkcd-prev
|
||
"r" #'xkcd-rand
|
||
"a" #'xkcd-rand ; because image-rotate can interfere
|
||
"t" #'xkcd-alt-text
|
||
"q" #'xkcd-kill-buffer
|
||
"o" #'xkcd-open-browser
|
||
"e" #'xkcd-open-explanation-browser
|
||
;; extras
|
||
"s" #'+xkcd-find-and-view
|
||
"/" #'+xkcd-find-and-view
|
||
"y" #'+xkcd-copy))
|
||
#+end_src
|
||
|
||
Let's also extend the functionality a whole bunch.
|
||
#+begin_src emacs-lisp
|
||
(after! xkcd
|
||
(require 'emacsql-sqlite)
|
||
|
||
(defun +xkcd-select ()
|
||
"Prompt the user for an xkcd using `completing-read' and `+xkcd-select-format'. Return the xkcd number or nil"
|
||
(let* (prompt-lines
|
||
(-dummy (maphash (lambda (key xkcd-info)
|
||
(push (+xkcd-select-format xkcd-info) prompt-lines))
|
||
+xkcd-stored-info))
|
||
(num (completing-read (format "xkcd (%s): " xkcd-latest) prompt-lines)))
|
||
(if (equal "" num) xkcd-latest
|
||
(string-to-number (replace-regexp-in-string "\\([0-9]+\\).*" "\\1" num)))))
|
||
|
||
(defun +xkcd-select-format (xkcd-info)
|
||
"Creates each completing-read line from an xkcd info plist. Must start with the xkcd number"
|
||
(format "%-4s %-30s %s"
|
||
(propertize (number-to-string (plist-get xkcd-info :num))
|
||
'face 'counsel-key-binding)
|
||
(plist-get xkcd-info :title)
|
||
(propertize (plist-get xkcd-info :alt)
|
||
'face '(variable-pitch font-lock-comment-face))))
|
||
|
||
(defun +xkcd-fetch-info (&optional num)
|
||
"Fetch the parsed json info for comic NUM. Fetches latest when omitted or 0"
|
||
(require 'xkcd)
|
||
(when (or (not num) (= num 0))
|
||
(+xkcd-check-latest)
|
||
(setq num xkcd-latest))
|
||
(let ((res (or (gethash num +xkcd-stored-info)
|
||
(puthash num (+xkcd-db-read num) +xkcd-stored-info))))
|
||
(unless res
|
||
(+xkcd-db-write
|
||
(let* ((url (format "https://xkcd.com/%d/info.0.json" num))
|
||
(json-assoc
|
||
(if (gethash num +xkcd-stored-info)
|
||
(gethash num +xkcd-stored-info)
|
||
(json-read-from-string (xkcd-get-json url num)))))
|
||
json-assoc))
|
||
(setq res (+xkcd-db-read num)))
|
||
res))
|
||
|
||
;; since we've done this, we may as well go one little step further
|
||
(defun +xkcd-find-and-copy ()
|
||
"Prompt for an xkcd using `+xkcd-select' and copy url to clipboard"
|
||
(interactive)
|
||
(+xkcd-copy (+xkcd-select)))
|
||
|
||
(defun +xkcd-copy (&optional num)
|
||
"Copy a url to xkcd NUM to the clipboard"
|
||
(interactive "i")
|
||
(let ((num (or num xkcd-cur)))
|
||
(gui-select-text (format "https://xkcd.com/%d" num))
|
||
(message "xkcd.com/%d copied to clipboard" num)))
|
||
|
||
(defun +xkcd-find-and-view ()
|
||
"Prompt for an xkcd using `+xkcd-select' and view it"
|
||
(interactive)
|
||
(xkcd-get (+xkcd-select))
|
||
(switch-to-buffer "*xkcd*"))
|
||
|
||
(defvar +xkcd-latest-max-age (* 60 60) ; 1 hour
|
||
"Time after which xkcd-latest should be refreshed, in seconds")
|
||
|
||
;; initialise `xkcd-latest' and `+xkcd-stored-info' with latest xkcd
|
||
(add-transient-hook! '+xkcd-select
|
||
(require 'xkcd)
|
||
(+xkcd-fetch-info xkcd-latest)
|
||
(setq +xkcd-stored-info (+xkcd-db-read-all)))
|
||
|
||
(add-transient-hook! '+xkcd-fetch-info
|
||
(xkcd-update-latest))
|
||
|
||
(defun +xkcd-check-latest ()
|
||
"Use value in `xkcd-cache-latest' as long as it isn't older thabn `+xkcd-latest-max-age'"
|
||
(unless (and (file-exists-p xkcd-cache-latest)
|
||
(< (- (time-to-seconds (current-time))
|
||
(time-to-seconds (file-attribute-modification-time (file-attributes xkcd-cache-latest))))
|
||
+xkcd-latest-max-age))
|
||
(let* ((out (xkcd-get-json "http://xkcd.com/info.0.json" 0))
|
||
(json-assoc (json-read-from-string out))
|
||
(latest (cdr (assoc 'num json-assoc))))
|
||
(when (/= xkcd-latest latest)
|
||
(+xkcd-db-write json-assoc)
|
||
(with-current-buffer (find-file xkcd-cache-latest)
|
||
(setq xkcd-latest latest)
|
||
(erase-buffer)
|
||
(insert (number-to-string latest))
|
||
(save-buffer)
|
||
(kill-buffer (current-buffer)))))
|
||
(shell-command (format "touch %s" xkcd-cache-latest))))
|
||
|
||
(defvar +xkcd-stored-info (make-hash-table :test 'eql)
|
||
"Basic info on downloaded xkcds, in the form of a hashtable")
|
||
|
||
(defadvice! xkcd-get-json--and-cache (url &optional num)
|
||
"Fetch the Json coming from URL.
|
||
If the file NUM.json exists, use it instead.
|
||
If NUM is 0, always download from URL.
|
||
The return value is a string."
|
||
:override #'xkcd-get-json
|
||
(let* ((file (format "%s%d.json" xkcd-cache-dir num))
|
||
(cached (and (file-exists-p file) (not (eq num 0))))
|
||
(out (with-current-buffer (if cached
|
||
(find-file file)
|
||
(url-retrieve-synchronously url))
|
||
(goto-char (point-min))
|
||
(unless cached (re-search-forward "^$"))
|
||
(prog1
|
||
(buffer-substring-no-properties (point) (point-max))
|
||
(kill-buffer (current-buffer))))))
|
||
(unless (or cached (eq num 0))
|
||
(xkcd-cache-json num out))
|
||
out))
|
||
|
||
(defadvice! +xkcd-get (num)
|
||
"Get the xkcd number NUM."
|
||
:override 'xkcd-get
|
||
(interactive "nEnter comic number: ")
|
||
(xkcd-update-latest)
|
||
(get-buffer-create "*xkcd*")
|
||
(switch-to-buffer "*xkcd*")
|
||
(xkcd-mode)
|
||
(let (buffer-read-only)
|
||
(erase-buffer)
|
||
(setq xkcd-cur num)
|
||
(let* ((xkcd-data (+xkcd-fetch-info num))
|
||
(num (plist-get xkcd-data :num))
|
||
(img (plist-get xkcd-data :img))
|
||
(safe-title (plist-get xkcd-data :safe-title))
|
||
(alt (plist-get xkcd-data :alt))
|
||
title file)
|
||
(message "Getting comic...")
|
||
(setq file (xkcd-download img num))
|
||
(setq title (format "%d: %s" num safe-title))
|
||
(insert (propertize title
|
||
'face 'outline-1))
|
||
(center-line)
|
||
(insert "\n")
|
||
(xkcd-insert-image file num)
|
||
(if (eq xkcd-cur 0)
|
||
(setq xkcd-cur num))
|
||
(setq xkcd-alt alt)
|
||
(message "%s" title))))
|
||
|
||
(defconst +xkcd-db--sqlite-available-p
|
||
(with-demoted-errors "+org-xkcd initialization: %S"
|
||
(emacsql-sqlite-ensure-binary)
|
||
t))
|
||
|
||
(defvar +xkcd-db--connection (make-hash-table :test #'equal)
|
||
"Database connection to +org-xkcd database.")
|
||
|
||
(defun +xkcd-db--get ()
|
||
"Return the sqlite db file."
|
||
(expand-file-name "xkcd.db" xkcd-cache-dir))
|
||
|
||
(defun +xkcd-db--get-connection ()
|
||
"Return the database connection, if any."
|
||
(gethash (file-truename xkcd-cache-dir)
|
||
+xkcd-db--connection))
|
||
|
||
(defconst +xkcd-db--table-schema
|
||
'((xkcds
|
||
[(num integer :unique :primary-key)
|
||
(year :not-null)
|
||
(month :not-null)
|
||
(link :not-null)
|
||
(news :not-null)
|
||
(safe_title :not-null)
|
||
(title :not-null)
|
||
(transcript :not-null)
|
||
(alt :not-null)
|
||
(img :not-null)])))
|
||
|
||
(defun +xkcd-db--init (db)
|
||
"Initialize database DB with the correct schema and user version."
|
||
(emacsql-with-transaction db
|
||
(pcase-dolist (`(,table . ,schema) +xkcd-db--table-schema)
|
||
(emacsql db [:create-table $i1 $S2] table schema))))
|
||
|
||
(defun +xkcd-db ()
|
||
"Entrypoint to the +org-xkcd sqlite database.
|
||
Initializes and stores the database, and the database connection.
|
||
Performs a database upgrade when required."
|
||
(unless (and (+xkcd-db--get-connection)
|
||
(emacsql-live-p (+xkcd-db--get-connection)))
|
||
(let* ((db-file (+xkcd-db--get))
|
||
(init-db (not (file-exists-p db-file))))
|
||
(make-directory (file-name-directory db-file) t)
|
||
(let ((conn (emacsql-sqlite db-file)))
|
||
(set-process-query-on-exit-flag (emacsql-process conn) nil)
|
||
(puthash (file-truename xkcd-cache-dir)
|
||
conn
|
||
+xkcd-db--connection)
|
||
(when init-db
|
||
(+xkcd-db--init conn)))))
|
||
(+xkcd-db--get-connection))
|
||
|
||
(defun +xkcd-db-query (sql &rest args)
|
||
"Run SQL query on +org-xkcd database with ARGS.
|
||
SQL can be either the emacsql vector representation, or a string."
|
||
(if (stringp sql)
|
||
(emacsql (+xkcd-db) (apply #'format sql args))
|
||
(apply #'emacsql (+xkcd-db) sql args)))
|
||
|
||
(defun +xkcd-db-read (num)
|
||
(when-let ((res
|
||
(car (+xkcd-db-query [:select * :from xkcds
|
||
:where (= num $s1)]
|
||
num
|
||
:limit 1))))
|
||
(+xkcd-db-list-to-plist res)))
|
||
|
||
(defun +xkcd-db-read-all ()
|
||
(let ((xkcd-table (make-hash-table :test 'eql :size 4000)))
|
||
(mapcar (lambda (xkcd-info-list)
|
||
(puthash (car xkcd-info-list) (+xkcd-db-list-to-plist xkcd-info-list) xkcd-table))
|
||
(+xkcd-db-query [:select * :from xkcds]))
|
||
xkcd-table))
|
||
|
||
(defun +xkcd-db-list-to-plist (xkcd-datalist)
|
||
`(:num ,(nth 0 xkcd-datalist)
|
||
:year ,(nth 1 xkcd-datalist)
|
||
:month ,(nth 2 xkcd-datalist)
|
||
:link ,(nth 3 xkcd-datalist)
|
||
:news ,(nth 4 xkcd-datalist)
|
||
:safe-title ,(nth 5 xkcd-datalist)
|
||
:title ,(nth 6 xkcd-datalist)
|
||
:transcript ,(nth 7 xkcd-datalist)
|
||
:alt ,(nth 8 xkcd-datalist)
|
||
:img ,(nth 9 xkcd-datalist)))
|
||
|
||
(defun +xkcd-db-write (data)
|
||
(+xkcd-db-query [:insert-into xkcds
|
||
:values $v1]
|
||
(list (vector
|
||
(cdr (assoc 'num data))
|
||
(cdr (assoc 'year data))
|
||
(cdr (assoc 'month data))
|
||
(cdr (assoc 'link data))
|
||
(cdr (assoc 'news data))
|
||
(cdr (assoc 'safe_title data))
|
||
(cdr (assoc 'title data))
|
||
(cdr (assoc 'transcript data))
|
||
(cdr (assoc 'alt data))
|
||
(cdr (assoc 'img data)))))))
|
||
#+end_src
|
||
|
||
Now to just have this register with org
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(org-link-set-parameters "xkcd"
|
||
:image-data-fun #'+org-xkcd-image-fn
|
||
:follow #'+org-xkcd-open-fn
|
||
:export #'+org-xkcd-export
|
||
:complete #'+org-xkcd-complete)
|
||
|
||
(defun +org-xkcd-open-fn (link)
|
||
(+org-xkcd-image-fn nil link nil))
|
||
|
||
(defun +org-xkcd-image-fn (protocol link description)
|
||
"Get image data for xkcd num LINK"
|
||
(let* ((xkcd-info (+xkcd-fetch-info (string-to-number link)))
|
||
(img (plist-get xkcd-info :img))
|
||
(alt (plist-get xkcd-info :alt)))
|
||
(message alt)
|
||
(+org-image-file-data-fn protocol (xkcd-download img (string-to-number link)) description)))
|
||
|
||
(defun +org-xkcd-export (num desc backend _com)
|
||
"Convert xkcd to html/LaTeX form"
|
||
(let* ((xkcd-info (+xkcd-fetch-info (string-to-number num)))
|
||
(img (plist-get xkcd-info :img))
|
||
(alt (plist-get xkcd-info :alt))
|
||
(title (plist-get xkcd-info :title))
|
||
(file (xkcd-download img (string-to-number num))))
|
||
(cond ((org-export-derived-backend-p backend 'html)
|
||
(format "<img class='invertible' src='%s' title=\"%s\" alt='%s'>" img (subst-char-in-string ?\" ?“ alt) title))
|
||
((org-export-derived-backend-p backend 'latex)
|
||
(format "\\begin{figure}[!htb]
|
||
\\centering
|
||
\\includegraphics[scale=0.4]{%s}%s
|
||
\\end{figure}" file (if (equal desc (format "xkcd:%s" num)) ""
|
||
(format "\n \\caption*{\\label{xkcd:%s} %s}"
|
||
num
|
||
(or desc
|
||
(format "\\textbf{%s} %s" title alt))))))
|
||
(t (format "https://xkcd.com/%s" num)))))
|
||
|
||
(defun +org-xkcd-complete (&optional arg)
|
||
"Complete xkcd using `+xkcd-stored-info'"
|
||
(format "xkcd:%d" (+xkcd-select))))
|
||
#+end_src
|
||
|
||
** COMMENT Calc
|
||
Embedded calc is a lovely feature which let's us use calc to operate on LaTeX
|
||
maths expressions. The standard keybinding is a bit janky however (=C-x * e=), so
|
||
we'll add a localleader-based alternative.
|
||
|
||
#+begin_src emacs-lisp
|
||
(map! :map calc-mode-map
|
||
:after calc
|
||
:localleader
|
||
:desc "Embedded calc (toggle)" "e" #'calc-embedded)
|
||
(map! :map org-mode-map
|
||
:after org
|
||
:localleader
|
||
:desc "Embedded calc (toggle)" "E" #'calc-embedded)
|
||
(map! :map latex-mode-map
|
||
:after latex
|
||
:localleader
|
||
:desc "Embedded calc (toggle)" "e" #'calc-embedded)
|
||
#+end_src
|
||
|
||
Unfortunately this operates without the (rather informative) calculator and
|
||
trail buffers, but we can advice it that we would rather like those in a side
|
||
panel.
|
||
|
||
#+begin_src emacs-lisp
|
||
(defvar calc-embedded-trail-window nil)
|
||
(defvar calc-embedded-calculator-window nil)
|
||
|
||
(defadvice! calc-embedded-with-side-pannel (&rest _)
|
||
:after #'calc-do-embedded
|
||
(when calc-embedded-trail-window
|
||
(ignore-errors
|
||
(delete-window calc-embedded-trail-window))
|
||
(setq calc-embedded-trail-window nil))
|
||
(when calc-embedded-calculator-window
|
||
(ignore-errors
|
||
(delete-window calc-embedded-calculator-window))
|
||
(setq calc-embedded-calculator-window nil))
|
||
(when (and calc-embedded-info
|
||
(> (* (window-width) (window-height)) 1200))
|
||
(let ((main-window (selected-window))
|
||
(vertical-p (> (window-width) 80)))
|
||
(select-window
|
||
(setq calc-embedded-trail-window
|
||
(if vertical-p
|
||
(split-window-horizontally (- (max 30 (/ (window-width) 3))))
|
||
(split-window-vertically (- (max 8 (/ (window-height) 4)))))))
|
||
(switch-to-buffer "*Calc Trail*")
|
||
(select-window
|
||
(setq calc-embedded-calculator-window
|
||
(if vertical-p
|
||
(split-window-vertically -6)
|
||
(split-window-horizontally (- (/ (window-width) 2))))))
|
||
(switch-to-buffer "*Calculator*")
|
||
(select-window main-window))))
|
||
#+end_src
|
||
|
||
*** Dictionaries
|
||
On every system I use (I have a lot of systems) the dictionary results in doom/emacs are different, and that gets annoying. Lets use lexic + stardict, instead of the default dictionaries.
|
||
#+begin_src emacs-lisp
|
||
(use-package! lexic
|
||
:commands lexic-search lexic-list-dictionary
|
||
:config
|
||
(map! :map lexic-mode-map
|
||
:n "q" #'lexic-return-from-lexic
|
||
:nv "RET" #'lexic-search-word-at-point
|
||
:n "a" #'outline-show-all
|
||
:n "h" (cmd! (outline-hide-sublevels 3))
|
||
:n "o" #'lexic-toggle-entry
|
||
:n "n" #'lexic-next-entry
|
||
:n "N" (cmd! (lexic-next-entry t))
|
||
:n "p" #'lexic-previous-entry
|
||
:n "P" (cmd! (lexic-previous-entry t))
|
||
:n "E" (cmd! (lexic-return-from-lexic) ; expand
|
||
(switch-to-buffer (lexic-get-buffer)))
|
||
:n "M" (cmd! (lexic-return-from-lexic) ; minimise
|
||
(lexic-goto-lexic))
|
||
:n "C-p" #'lexic-search-history-backwards
|
||
:n "C-n" #'lexic-search-history-forwards
|
||
:n "/" (cmd! (call-interactively #'lexic-search))))
|
||
|
||
(defadvice! +lookup/dictionary-definition-lexic (identifier &optional arg)
|
||
"Look up the definition of the word at point (or selection) using `lexic-search'."
|
||
:override #'+lookup/dictionary-definition
|
||
(interactive
|
||
(list (or (doom-thing-at-point-or-region 'word)
|
||
(read-string "Look up in dictionary: "))
|
||
current-prefix-arg))
|
||
(lexic-search identifier nil nil t))
|
||
#+end_src
|
||
|
||
* COMMENT LaTeX
|
||
Note that this \(\LaTeX\) export configuration is largely borrowed from Tecosaur and Elken, and just adjusted to fit my needs. Big thanks to them for their excellent configs.
|
||
** PDF-Tools
|
||
DocView gives me a headache, but pdf-tools can be improved, lets configure it a little more
|
||
#+begin_src emacs-lisp
|
||
(use-package! pdf-view
|
||
:hook (pdf-tools-enabled . pdf-view-themed-minor-mode)
|
||
:config
|
||
(setq pdf-view-use-scaling t
|
||
pdf-view-use-imagemagick nil
|
||
pdf-view-display-size 'fit-page))
|
||
#+end_src
|
||
|
||
*** View Exported File
|
||
I have to export files pretty often, lets setup some keybindings to make it easier
|
||
#+begin_src emacs-lisp
|
||
(map! :map org-mode-map
|
||
:localleader
|
||
:desc "View exported file" "v" #'org-view-output-file)
|
||
|
||
(defun org-view-output-file (&optional org-file-path)
|
||
"Visit buffer open on the first output file (if any) found, using `org-view-output-file-extensions'"
|
||
(interactive)
|
||
(let* ((org-file-path (or org-file-path (buffer-file-name) ""))
|
||
(dir (file-name-directory org-file-path))
|
||
(basename (file-name-base org-file-path))
|
||
(output-file nil))
|
||
(dolist (ext org-view-output-file-extensions)
|
||
(unless output-file
|
||
(when (file-exists-p
|
||
(concat dir basename "." ext))
|
||
(setq output-file (concat dir basename "." ext)))))
|
||
(if output-file
|
||
(if (member (file-name-extension output-file) org-view-external-file-extensions)
|
||
(browse-url-xdg-open output-file)
|
||
(pop-to-bufferpop-to-buffer (or (find-buffer-visiting output-file)
|
||
(find-file-noselect output-file))))
|
||
(message "No exported file found"))))
|
||
|
||
(defvar org-view-output-file-extensions '("pdf" "md" "rst" "txt" "tex" "html")
|
||
"Search for output files with these extensions, in order, viewing the first that matches")
|
||
(defvar org-view-external-file-extensions '("html")
|
||
"File formats that should be opened externally.")
|
||
#+end_src
|
||
|
||
** \(\LaTeX\) highlighting in Org-mode
|
||
The default inline latex highlighting is a bit bland. Normally this would be fixed with the =+pretty= flag, but that has its own issues. Lets just stick to enabling it manually.
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-highlight-latex-and-related '(native script entities))
|
||
(add-to-list 'org-src-block-faces '("latex" (:inherit default :extend t))))
|
||
#+end_src
|
||
|
||
Our org-mode config gets a bit expensive though. Lets make a small mode with just the basics, for exporting
|
||
#+begin_src emacs-lisp
|
||
(defun +org-mode--fontlock-only-mode ()
|
||
"Just apply org-mode's font-lock once."
|
||
(let (org-mode-hook
|
||
org-hide-leading-stars
|
||
org-hide-emphasis-markers)
|
||
(org-set-font-lock-defaults)
|
||
(font-lock-ensure))
|
||
(setq-local major-mode #'fundamental-mode))
|
||
|
||
(defun +org-export-babel-mask-org-config (_backend)
|
||
"Use `+org-mode--fontlock-only-mode' instead of `org-mode'."
|
||
(setq-local org-src-lang-modes
|
||
(append org-src-lang-modes
|
||
(list (cons "org" #'+org-mode--fontlock-only)))))
|
||
|
||
(add-hook 'org-export-before-processing-hook #'+org-export-babel-mask-org-config)
|
||
#+end_src
|
||
|
||
** Tectonic
|
||
Tectonic is the hot new thing, which also means I can get rid of my tex installation.
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq-default org-latex-pdf-process '("tectonic -Z shell-escape --outdir=%o %f")))
|
||
#+end_src
|
||
|
||
Looks crisp!
|
||
\begin{align*}
|
||
f(x) &= x^2\\
|
||
g(x) &= \frac{1}{x}\\
|
||
F(x) &= \int^a_b \frac{1}{3}x^3
|
||
\end{align*}
|
||
|
||
** Preambles
|
||
Various preamble setups to improve the overall look of several items
|
||
#+begin_src emacs-lisp
|
||
(defvar org-latex-caption-preamble "
|
||
\\usepackage{subcaption}
|
||
\\usepackage[hypcap=true]{caption}
|
||
\\setkomafont{caption}{\\sffamily\\small}
|
||
\\setkomafont{captionlabel}{\\upshape\\bfseries}
|
||
\\captionsetup{justification=raggedright,singlelinecheck=true}
|
||
\\usepackage{capt-of} % required by Org
|
||
"
|
||
"Preamble that improves captions.")
|
||
|
||
(defvar org-latex-checkbox-preamble "
|
||
\\newcommand{\\checkboxUnchecked}{$\\square$}
|
||
\\newcommand{\\checkboxTransitive}{\\rlap{\\raisebox{-0.1ex}{\\hspace{0.35ex}\\Large\\textbf -}}$\\square$}
|
||
\\newcommand{\\checkboxChecked}{\\rlap{\\raisebox{0.2ex}{\\hspace{0.35ex}\\scriptsize \\ding{52}}}$\\square$}
|
||
"
|
||
"Preamble that improves checkboxes.")
|
||
|
||
(defvar org-latex-box-preamble "
|
||
% args = #1 Name, #2 Colour, #3 Ding, #4 Label
|
||
\\newcommand{\\defsimplebox}[4]{%
|
||
\\definecolor{#1}{HTML}{#2}
|
||
\\newenvironment{#1}[1][]
|
||
{%
|
||
\\par\\vspace{-0.7\\baselineskip}%
|
||
\\textcolor{#1}{#3} \\textcolor{#1}{\\textbf{\\def\\temp{##1}\\ifx\\temp\\empty#4\\else##1\\fi}}%
|
||
\\vspace{-0.8\\baselineskip}
|
||
\\begin{addmargin}[1em]{1em}
|
||
}{%
|
||
\\end{addmargin}
|
||
\\vspace{-0.5\\baselineskip}
|
||
}%
|
||
}
|
||
"
|
||
"Preamble that provides a macro for custom boxes.")
|
||
#+end_src
|
||
|
||
** Conditional features
|
||
Don't always need everything in LaTeX, so only add it what we need when we need it.
|
||
#+begin_src emacs-lisp
|
||
(defvar org-latex-italic-quotes t
|
||
"Make \"quote\" environments italic.")
|
||
(defvar org-latex-par-sep t
|
||
"Vertically seperate paragraphs, and remove indentation.")
|
||
|
||
(defvar org-latex-conditional-features
|
||
'(("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]\\|\\\\\\]\\)+?\\.\\(?:eps\\|pdf\\|png\\|jpeg\\|jpg\\|jbig2\\)\\]\\]" . image)
|
||
("\\[\\[\\(?:file\\|https?\\):\\(?:[^]]+?\\|\\\\\\]\\)\\.svg\\]\\]\\|\\\\includesvg" . svg)
|
||
("^[ \t]*|" . table)
|
||
("cref:\\|\\cref{\\|\\[\\[[^\\]]+\\]\\]" . cleveref)
|
||
("[;\\\\]?\\b[A-Z][A-Z]+s?[^A-Za-z]" . acronym)
|
||
("\\+[^ ].*[^ ]\\+\\|_[^ ].*[^ ]_\\|\\\\uu?line\\|\\\\uwave\\|\\\\sout\\|\\\\xout\\|\\\\dashuline\\|\\dotuline\\|\\markoverwith" . underline)
|
||
(":float wrap" . float-wrap)
|
||
(":float sideways" . rotate)
|
||
("^[ \t]*#\\+caption:\\|\\\\caption" . caption)
|
||
("\\[\\[xkcd:" . (image caption))
|
||
((and org-latex-italic-quotes "^[ \t]*#\\+begin_quote\\|\\\\begin{quote}") . italic-quotes)
|
||
(org-latex-par-sep . par-sep)
|
||
("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\|[A-Za-z]+[.)]\\) \\[[ -X]\\]" . checkbox)
|
||
("^[ \t]*#\\+begin_warning\\|\\\\begin{warning}" . box-warning)
|
||
("^[ \t]*#\\+begin_info\\|\\\\begin{info}" . box-info)
|
||
("^[ \t]*#\\+begin_success\\|\\\\begin{success}" . box-success)
|
||
("^[ \t]*#\\+begin_error\\|\\\\begin{error}" . box-error))
|
||
"Org feature tests and associated LaTeX feature flags.
|
||
|
||
Alist where the car is a test for the presense of the feature,
|
||
and the cdr is either a single feature symbol or list of feature symbols.
|
||
|
||
When a string, it is used as a regex search in the buffer.
|
||
The feature is registered as present when there is a match.
|
||
|
||
The car can also be a
|
||
- symbol, the value of which is fetched
|
||
- function, which is called with info as an argument
|
||
- list, which is `eval'uated
|
||
|
||
If the symbol, function, or list produces a string: that is used as a regex
|
||
search in the buffer. Otherwise any non-nil return value will indicate the
|
||
existance of the feature.")
|
||
|
||
(defvar org-latex-feature-implementations
|
||
'((image :snippet "\\usepackage{graphicx}" :order 2)
|
||
(svg :snippet "\\usepackage{svg}" :order 2)
|
||
(table :snippet "\\usepackage{longtable}\n\\usepackage{booktabs}" :order 2)
|
||
(cleveref :snippet "\\usepackage[capitalize]{cleveref}" :order 1)
|
||
(underline :snippet "\\usepackage[normalem]{ulem}" :order 0.5)
|
||
(float-wrap :snippet "\\usepackage{wrapfig}" :order 2)
|
||
(rotate :snippet "\\usepackage{rotating}" :order 2)
|
||
(caption :snippet org-latex-caption-preamble :order 2.1)
|
||
(acronym :snippet "\\newcommand{\\acr}[1]{\\protect\\textls*[110]{\\scshape #1}}\n\\newcommand{\\acrs}{\\protect\\scalebox{.91}[.84]{\\hspace{0.15ex}s}}" :order 0.4)
|
||
(italic-quotes :snippet "\\renewcommand{\\quote}{\\list{}{\\rightmargin\\leftmargin}\\item\\relax\\em}\n" :order 0.5)
|
||
(par-sep :snippet "\\setlength{\\parskip}{\\baselineskip}\n\\setlength{\\parindent}{0pt}\n" :order 0.5)
|
||
(.pifont :snippet "\\usepackage{pifont}")
|
||
(checkbox :requires .pifont :order 3
|
||
:snippet (concat (unless (memq 'maths features)
|
||
"\\usepackage{amssymb} % provides \\square")
|
||
org-latex-checkbox-preamble))
|
||
(.fancy-box :requires .pifont :snippet org-latex-box-preamble :order 3.9)
|
||
(box-warning :requires .fancy-box :snippet "\\defsimplebox{warning}{e66100}{\\ding{68}}{Warning}" :order 4)
|
||
(box-info :requires .fancy-box :snippet "\\defsimplebox{info}{3584e4}{\\ding{68}}{Information}" :order 4)
|
||
(box-success :requires .fancy-box :snippet "\\defsimplebox{success}{26a269}{\\ding{68}}{\\vspace{-\\baselineskip}}" :order 4)
|
||
(box-error :requires .fancy-box :snippet "\\defsimplebox{error}{c01c28}{\\ding{68}}{Important}" :order 4))
|
||
"LaTeX features and details required to implement them.
|
||
|
||
List where the car is the feature symbol, and the rest forms a plist with the
|
||
following keys:
|
||
- :snippet, which may be either
|
||
- a string which should be included in the preamble
|
||
- a symbol, the value of which is included in the preamble
|
||
- a function, which is evaluated with the list of feature flags as its
|
||
single argument. The result of which is included in the preamble
|
||
- a list, which is passed to `eval', with a list of feature flags available
|
||
as \"features\"
|
||
|
||
- :requires, a feature or list of features that must be available
|
||
- :when, a feature or list of features that when all available should cause this
|
||
to be automatically enabled.
|
||
- :prevents, a feature or list of features that should be masked
|
||
- :order, for when ordering is important. Lower values appear first.
|
||
The default is 0.
|
||
|
||
Features that start with ! will be eagerly loaded, i.e. without being detected.")
|
||
#+end_src
|
||
|
||
First, we need to detect which features we actually need
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun org-latex-detect-features (&optional buffer info)
|
||
"List features from `org-latex-conditional-features' detected in BUFFER."
|
||
(let ((case-fold-search nil))
|
||
(with-current-buffer (or buffer (current-buffer))
|
||
(delete-dups
|
||
(mapcan (lambda (construct-feature)
|
||
(when (let ((out (pcase (car construct-feature)
|
||
((pred stringp) (car construct-feature))
|
||
((pred functionp) (funcall (car construct-feature) info))
|
||
((pred listp) (eval (car construct-feature)))
|
||
((pred symbolp) (symbol-value (car construct-feature)))
|
||
(_ (user-error "org-latex-conditional-features key %s unable to be used" (car construct-feature))))))
|
||
(if (stringp out)
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
(re-search-forward out nil t))
|
||
out))
|
||
(if (listp (cdr construct-feature)) (cdr construct-feature) (list (cdr construct-feature)))))
|
||
org-latex-conditional-features)))))
|
||
#+end_src
|
||
|
||
Then we need to expand them and sort them according to the above definitions
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun org-latex-expand-features (features)
|
||
"For each feature in FEATURES process :requires, :when, and :prevents keywords and sort according to :order."
|
||
(dolist (feature features)
|
||
(unless (assoc feature org-latex-feature-implementations)
|
||
(error "Feature %s not provided in org-latex-feature-implementations" feature)))
|
||
(setq current features)
|
||
(while current
|
||
(when-let ((requirements (plist-get (cdr (assq (car current) org-latex-feature-implementations)) :requires)))
|
||
(setcdr current (if (listp requirements)
|
||
(append requirements (cdr current))
|
||
(cons requirements (cdr current)))))
|
||
(setq current (cdr current)))
|
||
(dolist (potential-feature
|
||
(append features (delq nil (mapcar (lambda (feat)
|
||
(when (plist-get (cdr feat) :eager)
|
||
(car feat)))
|
||
org-latex-feature-implementations))))
|
||
(when-let ((prerequisites (plist-get (cdr (assoc potential-feature org-latex-feature-implementations)) :when)))
|
||
(setf features (if (if (listp prerequisites)
|
||
(cl-every (lambda (preq) (memq preq features)) prerequisites)
|
||
(memq prerequisites features))
|
||
(append (list potential-feature) features)
|
||
(delq potential-feature features)))))
|
||
(dolist (feature features)
|
||
(when-let ((prevents (plist-get (cdr (assoc feature org-latex-feature-implementations)) :prevents)))
|
||
(setf features (cl-set-difference features (if (listp prevents) prevents (list prevents))))))
|
||
(sort (delete-dups features)
|
||
(lambda (feat1 feat2)
|
||
(if (< (or (plist-get (cdr (assoc feat1 org-latex-feature-implementations)) :order) 1)
|
||
(or (plist-get (cdr (assoc feat2 org-latex-feature-implementations)) :order) 1))
|
||
t nil))))
|
||
#+end_src
|
||
|
||
Finally, we can create the preamble to be inserted
|
||
|
||
#+begin_src emacs-lisp
|
||
(defun org-latex-generate-features-preamble (features)
|
||
"Generate the LaTeX preamble content required to provide FEATURES.
|
||
This is done according to `org-latex-feature-implementations'"
|
||
(let ((expanded-features (org-latex-expand-features features)))
|
||
(concat
|
||
(format "\n%% features: %s\n" expanded-features)
|
||
(mapconcat (lambda (feature)
|
||
(when-let ((snippet (plist-get (cdr (assoc feature org-latex-feature-implementations)) :snippet)))
|
||
(concat
|
||
(pcase snippet
|
||
((pred stringp) snippet)
|
||
((pred functionp) (funcall snippet features))
|
||
((pred listp) (eval `(let ((features ',features)) (,@snippet))))
|
||
((pred symbolp) (symbol-value snippet))
|
||
(_ (user-error "org-latex-feature-implementations :snippet value %s unable to be used" snippet)))
|
||
"\n")))
|
||
expanded-features
|
||
"")
|
||
"% end features\n")))
|
||
#+end_src
|
||
|
||
Last step, some advice to hook in all of the above to work
|
||
#+begin_src emacs-lisp
|
||
(defvar info--tmp nil)
|
||
|
||
(defadvice! org-latex-save-info (info &optional t_ s_)
|
||
:before #'org-latex-make-preamble
|
||
(setq info--tmp info))
|
||
|
||
(defadvice! org-splice-latex-header-and-generated-preamble-a (orig-fn tpl def-pkg pkg snippets-p &optional extra)
|
||
"Dynamically insert preamble content based on `org-latex-conditional-preambles'."
|
||
:around #'org-splice-latex-header
|
||
(let ((header (funcall orig-fn tpl def-pkg pkg snippets-p extra)))
|
||
(if snippets-p header
|
||
(concat header
|
||
(org-latex-generate-features-preamble (org-latex-detect-features nil info--tmp))
|
||
"\n"))))
|
||
#+end_src
|
||
|
||
** Class templates
|
||
I really like the KOMA bundle. It provides a set of mechanisms to tweak document
|
||
styling which is both easy to use, and quite comprehensive.
|
||
For example, I rather like section numbers in the margin, which can be
|
||
accomplished with
|
||
#+begin_src emacs-lisp
|
||
(after! ox-latex
|
||
(let* ((article-sections '(("\\section{%s}" . "\\section*{%s}")
|
||
("\\subsection{%s}" . "\\subsection*{%s}")
|
||
("\\subsubsection{%s}" . "\\subsubsection*{%s}")
|
||
("\\paragraph{%s}" . "\\paragraph*{%s}")
|
||
("\\subparagraph{%s}" . "\\subparagraph*{%s}")))
|
||
(book-sections (append '(("\\chapter{%s}" . "\\chapter*{%s}"))
|
||
article-sections))
|
||
(hanging-secnum-preamble "
|
||
\\renewcommand\\sectionformat{\\llap{\\thesection\\autodot\\enskip}}
|
||
\\renewcommand\\subsectionformat{\\llap{\\thesubsection\\autodot\\enskip}}
|
||
\\renewcommand\\subsubsectionformat{\\llap{\\thesubsubsection\\autodot\\enskip}}
|
||
")
|
||
(big-chap-preamble "
|
||
\\RedeclareSectionCommand[afterindent=false, beforeskip=0pt, afterskip=0pt, innerskip=0pt]{chapter}
|
||
\\setkomafont{chapter}{\\normalfont\\Huge}
|
||
\\renewcommand*{\\chapterheadstartvskip}{\\vspace*{0\\baselineskip}}
|
||
\\renewcommand*{\\chapterheadendvskip}{\\vspace*{0\\baselineskip}}
|
||
\\renewcommand*{\\chapterformat}{%
|
||
\\fontsize{60}{30}\\selectfont\\rlap{\\hspace{6pt}\\thechapter}}
|
||
\\renewcommand*\\chapterlinesformat[3]{%
|
||
\\parbox[b]{\\dimexpr\\textwidth-0.5em\\relax}{%
|
||
\\raggedleft{{\\large\\scshape\\bfseries\\chapapp}\\vspace{-0.5ex}\\par\\Huge#3}}%
|
||
\\hfill\\makebox[0pt][l]{#2}}
|
||
"))
|
||
(setcdr (assoc "article" org-latex-classes)
|
||
`(,(concat "\\documentclass{scrartcl}" hanging-secnum-preamble)
|
||
,@article-sections))
|
||
(add-to-list 'org-latex-classes
|
||
`("report" ,(concat "\\documentclass{scrartcl}" hanging-secnum-preamble)
|
||
,@article-sections))
|
||
(add-to-list 'org-latex-classes
|
||
`("book" ,(concat "\\documentclass[twoside=false]{scrbook}"
|
||
big-chap-preamble hanging-secnum-preamble)
|
||
,@book-sections))
|
||
(add-to-list 'org-latex-classes
|
||
`("blank" "[NO-DEFAULT-PACKAGES]\n[NO-PACKAGES]\n[EXTRA]"
|
||
,@article-sections))
|
||
(add-to-list 'org-latex-classes
|
||
`("bmc-article" "\\documentclass[article,code,maths]{bmc}\n[NO-DEFAULT-PACKAGES]\n[NO-PACKAGES]\n[EXTRA]"
|
||
,@article-sections))
|
||
(add-to-list 'org-latex-classes
|
||
`("bmc" "\\documentclass[code,maths]{bmc}\n[NO-DEFAULT-PACKAGES]\n[NO-PACKAGES]\n[EXTRA]"
|
||
,@book-sections))))
|
||
|
||
(after! ox-latex
|
||
(setq org-latex-tables-booktabs t
|
||
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}
|
||
"
|
||
org-latex-reference-command "\\cref{%s}"))
|
||
#+end_src
|
||
|
||
** Adjust default packages
|
||
Thanks to our additions, we can remove a few packages from
|
||
~org-latex-default-packages-alist~.
|
||
|
||
There are also some obsolete entries in the default value, specifically
|
||
- =grffile='s capabilities are built into the current version of =graphicx=
|
||
- =textcomp='s functionality has been included in LaTeX's core for a while now
|
||
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(setq org-latex-default-packages-alist
|
||
'(("AUTO" "inputenc" t ("pdflatex"))
|
||
("T1" "fontenc" t ("pdflatex"))
|
||
("" "fontspec" t)
|
||
("" "xcolor" nil)
|
||
("" "hyperref" nil)
|
||
("" "firamath-otf" t)
|
||
"\\setmonofont{Liga SFMono Nerd Font}"
|
||
"\\setmainfont{IBM Plex Sans}")))
|
||
#+end_src
|
||
|
||
** Cover page
|
||
To make a nice cover page, a simple method that comes to mind is just redefining
|
||
=\maketitle=. To get precise control over the positioning we'll use the =tikz=
|
||
package, and then add in the Tikz libraries =calc= and =shapes.geometric= to make
|
||
some nice decorations for the background.
|
||
|
||
I'll start off by setting up the required additions to the preamble.
|
||
This will accomplish the following:
|
||
- Load the required packages
|
||
- Redefine =\maketitle=
|
||
- Draw an Org icon with Tikz to use in the cover page (it's a little easter egg)
|
||
- Start a new page after the table of contents by redefining =\tableofcontents=
|
||
|
||
Now we've got a nice cover page to work with, we just need to use it every now
|
||
and then. Adding this to =#+options= feels most appropriate.
|
||
Let's have the =coverpage= option accept =auto= as a value and then decide whether
|
||
or not a cover page should be used based on the word count --- I'll have this be
|
||
the global default. Then we just want to insert a LaTeX snippet tweak the
|
||
subtitle format to use the cover page.
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(defvar org-latex-cover-page 'auto
|
||
"When t, use a cover page by default.
|
||
When auto, use a cover page when the document's wordcount exceeds
|
||
`org-latex-cover-page-wordcount-threshold'.
|
||
|
||
Set with #+option: coverpage:{yes,auto,no} in org buffers.")
|
||
(defvar org-latex-cover-page-wordcount-threshold 5000
|
||
"Document word count at which a cover page will be used automatically.
|
||
This condition is applied when cover page option is set to auto.")
|
||
(defvar org-latex-subtitle-coverpage-format "\\\\\\bigskip\n\\LARGE\\mdseries\\itshape\\color{black!80} %s\\par"
|
||
"Variant of `org-latex-subtitle-format' to use with the cover page.")
|
||
(defvar org-latex-cover-page-maketitle "
|
||
\\usepackage{tikz}
|
||
\\usetikzlibrary{shapes.geometric}
|
||
\\usetikzlibrary{calc}
|
||
|
||
\\newsavebox\\orgicon
|
||
\\begin{lrbox}{\\orgicon}
|
||
\\begin{tikzpicture}[y=0.80pt, x=0.80pt, inner sep=0pt, outer sep=0pt]
|
||
\\path[fill=black!6] (16.15,24.00) .. controls (15.58,24.00) and (13.99,20.69) .. (12.77,18.06)arc(215.55:180.20:2.19) .. controls (12.33,19.91) and (11.27,19.09) .. (11.43,18.05) .. controls (11.36,18.09) and (10.17,17.83) .. (10.17,17.82) .. controls (9.94,18.75) and (9.37,19.44) .. (9.02,18.39) .. controls (8.32,16.72) and (8.14,15.40) .. (9.13,13.80) .. controls (8.22,9.74) and (2.18,7.75) .. (2.81,4.47) .. controls (2.99,4.47) and (4.45,0.99) .. (9.15,2.41) .. controls (14.71,3.99) and (17.77,0.30) .. (18.13,0.04) .. controls (18.65,-0.49) and (16.78,4.61) .. (12.83,6.90) .. controls (10.49,8.18) and (11.96,10.38) .. (12.12,11.15) .. controls (12.12,11.15) and (14.00,9.84) .. (15.36,11.85) .. controls (16.58,11.53) and (17.40,12.07) .. (18.46,11.69) .. controls (19.10,11.41) and (21.79,11.58) .. (20.79,13.08) .. controls (20.79,13.08) and (21.71,13.90) .. (21.80,13.99) .. controls (21.97,14.75) and (21.59,14.91) .. (21.47,15.12) .. controls (21.44,15.60) and (21.04,15.79) .. (20.55,15.44) .. controls (19.45,15.64) and (18.36,15.55) .. (17.83,15.59) .. controls (16.65,15.76) and (15.67,16.38) .. (15.67,16.38) .. controls (15.40,17.19) and (14.82,17.01) .. (14.09,17.32) .. controls (14.70,18.69) and (14.76,19.32) .. (15.50,21.32) .. controls (15.76,22.37) and (16.54,24.00) .. (16.15,24.00) -- cycle(7.83,16.74) .. controls (6.83,15.71) and (5.72,15.70) .. (4.05,15.42) .. controls (2.75,15.19) and (0.39,12.97) .. (0.02,10.68) .. controls (-0.02,10.07) and (-0.06,8.50) .. (0.45,7.18) .. controls (0.94,6.05) and (1.27,5.45) .. (2.29,4.85) .. controls (1.41,8.02) and (7.59,10.18) .. (8.55,13.80) -- (8.55,13.80) .. controls (7.73,15.00) and (7.80,15.64) .. (7.83,16.74) -- cycle;
|
||
\\end{tikzpicture}
|
||
\\end{lrbox}
|
||
|
||
\\makeatletter
|
||
\\g@addto@macro\\tableofcontents{\\clearpage}
|
||
\\renewcommand\\maketitle{
|
||
\\thispagestyle{empty}
|
||
\\hyphenpenalty=10000 % hyphens look bad in titles
|
||
\\renewcommand{\\baselinestretch}{1.1}
|
||
\\let\\oldtoday\\today
|
||
\\renewcommand{\\today}{\\LARGE\\number\\year\\\\\\large%
|
||
\\ifcase \\month \\or Jan\\or Feb\\or Mar\\or Apr\\or May \\or Jun\\or Jul\\or Aug\\or Sep\\or Oct\\or Nov\\or Dec\\fi
|
||
~\\number\\day}
|
||
\\begin{tikzpicture}[remember picture,overlay]
|
||
%% Background Polygons %%
|
||
\\foreach \\i in {2.5,...,22} % bottom left
|
||
{\\node[rounded corners,black!3.5,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.west)+(2.5,-4.2)$) {} ;}
|
||
\\foreach \\i in {0.5,...,22} % top left
|
||
{\\node[rounded corners,black!5,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.north west)+(2.5,2)$) {} ;}
|
||
\\node[rounded corners,fill=black!4,regular polygon,regular polygon sides=6, minimum size=5.5 cm,ultra thick] at ($(current page.north west)+(2.5,2)$) {};
|
||
\\foreach \\i in {0.5,...,24} % top right
|
||
{\\node[rounded corners,black!2,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.north east)+(0,-8.5)$) {} ;}
|
||
\\node[fill=black!3,rounded corners,regular polygon,regular polygon sides=6, minimum size=2.5 cm,ultra thick] at ($(current page.north east)+(0,-8.5)$) {};
|
||
\\foreach \\i in {21,...,3} % bottom right
|
||
{\\node[black!3,rounded corners,draw,regular polygon,regular polygon sides=6, minimum size=\\i cm,ultra thick] at ($(current page.south east)+(-1.5,0.75)$) {} ;}
|
||
\\node[fill=black!3,rounded corners,regular polygon,regular polygon sides=6, minimum size=2 cm,ultra thick] at ($(current page.south east)+(-1.5,0.75)$) {};
|
||
\\node[align=center, scale=1.4] at ($(current page.south east)+(-1.5,0.75)$) {\\usebox\\orgicon};
|
||
%% Text %%
|
||
\\node[left, align=right, black, text width=0.8\\paperwidth, minimum height=3cm, rounded corners,font=\\Huge\\bfseries] at ($(current page.north east)+(-2,-8.5)$)
|
||
{\\@title};
|
||
\\node[left, align=right, black, text width=0.8\\paperwidth, minimum height=2cm, rounded corners, font=\\Large] at ($(current page.north east)+(-2,-11.8)$)
|
||
{\\scshape \\@author};
|
||
\\renewcommand{\\baselinestretch}{0.75}
|
||
\\node[align=center,rounded corners,fill=black!3,text=black,regular polygon,regular polygon sides=6, minimum size=2.5 cm,inner sep=0, font=\\Large\\bfseries ] at ($(current page.west)+(2.5,-4.2)$)
|
||
{\\@date};
|
||
\\end{tikzpicture}
|
||
\\let\\today\\oldtoday
|
||
\\clearpage}
|
||
\\makeatother
|
||
"
|
||
"LaTeX snippet for the preamble that sets \\maketitle to produce a cover page.")
|
||
(after! ox
|
||
(eval '(cl-pushnew '(:latex-cover-page nil "coverpage" org-latex-cover-page)
|
||
(org-export-backend-options (org-export-get-backend 'latex)))))
|
||
|
||
(defun org-latex-cover-page-p ()
|
||
"Whether a cover page should be used when exporting this Org file."
|
||
(pcase (or (car
|
||
(delq nil
|
||
(mapcar
|
||
(lambda (opt-line)
|
||
(plist-get (org-export--parse-option-keyword opt-line 'latex) :latex-cover-page))
|
||
(cdar (org-collect-keywords '("OPTIONS"))))))
|
||
org-latex-cover-page)
|
||
((or 't 'yes) t)
|
||
('auto (when (> (count-words (point-min) (point-max)) org-latex-cover-page-wordcount-threshold) t))
|
||
(_ nil)))
|
||
|
||
(defadvice! org-latex-set-coverpage-subtitle-format-a (contents info)
|
||
"Set the subtitle format when a cover page is being used."
|
||
:before #'org-latex-template
|
||
(when (org-latex-cover-page-p)
|
||
(setf info (plist-put info :latex-subtitle-format org-latex-subtitle-coverpage-format))))
|
||
|
||
(add-to-list 'org-latex-feature-implementations '(cover-page :snippet org-latex-cover-page-maketitle :order 9) t)
|
||
(add-to-list 'org-latex-conditional-features '((org-latex-cover-page-p) . cover-page) t))
|
||
#+end_src
|
||
|
||
** Condensed lists
|
||
LaTeX is generally pretty good by default, but it's /really/ generous with how
|
||
much space it puts between list items by default. I'm generally not a fan.
|
||
|
||
Thankfully this is easy to correct with a small snippet:
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(defvar org-latex-condense-lists t
|
||
"Reduce the space between list items.")
|
||
(defvar org-latex-condensed-lists "
|
||
\\newcommand{\\setuplistspacing}{\\setlength{\\itemsep}{-0.5ex}\\setlength{\\parskip}{1.5ex}\\setlength{\\parsep}{0pt}}
|
||
\\let\\olditem\\itemize\\renewcommand{\\itemize}{\\olditem\\setuplistspacing}
|
||
\\let\\oldenum\\enumerate\\renewcommand{\\enumerate}{\\oldenum\\setuplistspacing}
|
||
\\let\\olddesc\\description\\renewcommand{\\description}{\\olddesc\\setuplistspacing}
|
||
")
|
||
|
||
(add-to-list 'org-latex-conditional-features '((and org-latex-condense-lists "^[ \t]*[-+]\\|^[ \t]*[1Aa][.)] ") . condensed-lists) t)
|
||
(add-to-list 'org-latex-feature-implementations '(condensed-lists :snippet org-latex-condensed-lists :order 0.7) t))
|
||
#+end_src
|
||
|
||
** Pretty code blocks
|
||
Ripped this off of Teco, thanks mate!
|
||
#+begin_src emacs-lisp
|
||
(use-package! engrave-faces-latex
|
||
:after ox-latex
|
||
:config
|
||
(setq org-latex-listings 'engraved))
|
||
#+end_src
|
||
|
||
#+begin_src emacs-lisp
|
||
(defvar-local org-export-has-code-p nil)
|
||
|
||
(defadvice! org-export-expect-no-code (&rest _)
|
||
:before #'org-export-as
|
||
(setq org-export-has-code-p nil))
|
||
|
||
(defadvice! org-export-register-code (&rest _)
|
||
:after #'org-latex-src-block
|
||
:after #'org-latex-inline-src-block-engraved
|
||
(setq org-export-has-code-p t))
|
||
|
||
(defadvice! org-latex-example-block-engraved (orig-fn example-block contents info)
|
||
"Like `org-latex-example-block', but supporting an engraved backend"
|
||
:around #'org-latex-example-block
|
||
(let ((output-block (funcall orig-fn example-block contents info)))
|
||
(if (eq 'engraved (plist-get info :latex-listings))
|
||
(format "\\begin{Code}[alt]\n%s\n\\end{Code}" output-block)
|
||
output-block)))
|
||
#+end_src
|
||
|
||
andn addition to the vastly superior visual output, this should also be much
|
||
faster to compile for code-heavy documents (like this config).
|
||
|
||
Performing a little benchmark with this document, I find that this is indeed the
|
||
case.
|
||
|
||
| LaTeX syntax highlighting backend | Compile time | Overhead | Overhead ratio |
|
||
|-----------------------------------+--------------+----------+----------------|
|
||
| verbatim | 12 s | 0 | 0.0 |
|
||
| lstlistings | 15 s | 3 s | 0.2 |
|
||
| Engrave | 34 s | 22 s | 1.8 |
|
||
| Pygments (Minted) | 184 s | 172 s | 14.3 |
|
||
#+TBLFM: $3=$2-@2$2::$4=$3 / @2$2;%.1f
|
||
|
||
Treating the verbatim (no syntax highlighting) result as a baseline; this
|
||
rudimentary test suggest that =engrave-faces= is around eight times faster than
|
||
=pygments=, and takes three times as long as no syntax highlighting (verbatim).
|
||
|
||
** Ox-chameleon
|
||
Match export with current emacs theme
|
||
#+begin_src emacs-lisp
|
||
(use-package! ox-chameleon
|
||
:after ox)
|
||
#+end_src
|
||
|
||
** Support images from URLs
|
||
You can link to remote images easily, and they work nicely with HTML-based
|
||
exports. However, LaTeX can only include local files, and so the current
|
||
behaviour of =org-latex-link= is just to insert a URL to the image.
|
||
|
||
We can do better than that by downloading the image to a predictable location,
|
||
and using that. By making the filename predictable as opposed to just another
|
||
tempfile, this can provide a caching mechanism.
|
||
|
||
#+begin_src emacs-lisp
|
||
(after! org
|
||
(defadvice! +org-latex-link (orig-fn link desc info)
|
||
"Acts as `org-latex-link', but supports remote images."
|
||
:around #'org-latex-link
|
||
(setq o-link link
|
||
o-desc desc
|
||
o-info info)
|
||
(if (and (member (plist-get (cadr link) :type) '("http" "https"))
|
||
(member (file-name-extension (plist-get (cadr link) :path))
|
||
'("png" "jpg" "jpeg" "pdf" "svg")))
|
||
(org-latex-link--remote link desc info)
|
||
(funcall orig-fn link desc info)))
|
||
|
||
(defun org-latex-link--remote (link _desc info)
|
||
(let* ((url (plist-get (cadr link) :raw-link))
|
||
(ext (file-name-extension url))
|
||
(target (format "%s%s.%s"
|
||
(temporary-file-directory)
|
||
(replace-regexp-in-string "[./]" "-"
|
||
(file-name-sans-extension (substring (plist-get (cadr link) :path) 2)))
|
||
ext)))
|
||
(unless (file-exists-p target)
|
||
(url-copy-file url target))
|
||
(setcdr link (--> (cadr link)
|
||
(plist-put it :type "file")
|
||
(plist-put it :path target)
|
||
(plist-put it :raw-link (concat "file:" target))
|
||
(list it)))
|
||
(concat "% fetched from " url "\n"
|
||
(org-latex--inline-image link info)))))
|
||
#+end_src
|
||
|
||
** Beamer Exports
|
||
I've also recently got into presenting more and more often, so its nice to have beamer do everything for us
|
||
#+begin_src emacs-lisp
|
||
(setq org-beamer-theme "[progressbar=foot]metropolis"
|
||
org-beamer-frame-level 2)
|
||
|
||
(defun org-beamer-p (info)
|
||
(eq 'beamer (and (plist-get info :back-end)
|
||
(org-export-backend-name (plist-get info :back-end)))))
|
||
|
||
(defvar org-beamer-metropolis-tweaks "
|
||
\\NewCommandCopy{\\moldusetheme}{\\usetheme}
|
||
\\renewcommand*{\\usetheme}[2][]{\\moldusetheme[#1]{#2}
|
||
\\setbeamertemplate{items}{$\\bullet$}
|
||
\\setbeamerfont{block title}{size=\\normalsize, series=\\bfseries\\parbox{0pt}{\\rule{0pt}{4ex}}}}
|
||
|
||
\\makeatletter
|
||
\\newcommand{\\setmetropolislinewidth}{
|
||
\\setlength{\\metropolis@progressinheadfoot@linewidth}{1.2px}}
|
||
\\makeatother
|
||
|
||
\\usepackage{etoolbox}
|
||
\\AtEndPreamble{\\setmetropolislinewidth}
|
||
")
|
||
|
||
(add-to-list 'org-latex-conditional-features '(org-beamer-p . beamer) t)
|
||
(add-to-list 'org-latex-feature-implementations '(beamer :requires .missing-koma :prevents (italic-quotes condensed-lists)) t)
|
||
(add-to-list 'org-latex-feature-implementations '(.missing-koma :snippet "\\usepackage{scrextend}" :order 2) t)
|
||
(add-to-list 'org-latex-conditional-features '((lambda (info) (and (org-beamer-p info) (string-match-p "metropolis$" org-beamer-theme))) . beamer-metropolis) t)
|
||
(add-to-list 'org-latex-feature-implementations '(beamer-metropolis :requires beamer :snippet org-beamer-metropolis-tweaks :order 3) t)
|
||
#+end_src
|