dot-emacs

My emacs dotfiles/config
git clone git://git.ethandl.dev/dot-emacs
Log | Files | Refs

config.org (35204B)


      1 #+title: Fast (il)literate emacs config built on ~elpaca~
      2 #+author: Ethan Long
      3 #+startup: overview
      4 
      5 * Overview of this config
      6 This is my (subjectively) simplistic literate config for Emacs.
      7 I aim to keep this minimal, and to use as few large dependencies as possible.
      8 
      9 I previously had a very complicated handwoven Emacs config with a beautiful ~org-mode~, but I have grown tired of the number of packages that is used, and I don't really use ~org~ that heavily.
     10 
     11 The reader may disagree on the minimalism of this configuration, which is rather subjective.
     12 If one wanted to be as minimal as possible, vanilla Emacs without any configuration technically functions, but that's not in the spirit of Emacs in my opinion.
     13 
     14 As an added bonus of being more minimal, this config should also be more portable across systems (I hope).
     15 
     16 This config has the following explicit package dependencies:
     17 |--------------+--------------------------------------------------------------------|
     18 | *Package*      | *Use*                                                                |
     19 |--------------+--------------------------------------------------------------------|
     20 | ~org~          | Karthinks build, required for LaTeX previews. Declared in ~init.el~. |
     21 |--------------+--------------------------------------------------------------------|
     22 | ~meow~         | For modal keybinds.                                                |
     23 |--------------+--------------------------------------------------------------------|
     24 | ~ligature~     | For font ligatures on regular Emacs builds.                        |
     25 |--------------+--------------------------------------------------------------------|
     26 | ~batppuccin~   | For the Catppuccin theme.                                          |
     27 |--------------+--------------------------------------------------------------------|
     28 | ~ultra-scroll~ | For better smooth scrolling when navigating with a mouse.          |
     29 |--------------+--------------------------------------------------------------------|
     30 | ~async~        | For the easy ability to start async processes.                     |
     31 |              | Used for async treesitter grammar building and installation.       |
     32 |--------------+--------------------------------------------------------------------|
     33 | ~org-modern~   | For a nicer looking Org mode in only 900 lines of elisp.           |
     34 |--------------+--------------------------------------------------------------------|
     35 | ~vterm~        | For a usable terminal emulator within Emacs.                       |
     36 |--------------+--------------------------------------------------------------------|
     37 | ~powershell~   | A small powershell mode for working with psh files over TRAMP.     |
     38 |--------------+--------------------------------------------------------------------|
     39 There are a total of 9 packages installed by these dependencies.
     40 * Keybinds
     41 ** Meow (modal editing)
     42 This package is very important to me, I have been poisoned by ~vi~ and rely on the modal editing model to function properly.
     43 If you're interested in my config function, see ~meow-setup~.
     44 I use mostly the recommended QWERTY binds given by the Meow devs:
     45 :meow-setup:
     46 #+begin_src emacs-lisp
     47   (defun ethandl/meow-setup ()
     48     (require 'meow)
     49     (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
     50     (setq meow-keypad-leader-dispatch "C-x")
     51     (meow-motion-overwrite-define-key
     52      '("j" . meow-next)
     53      '("k" . meow-prev)
     54      '("<escape>" . ignore))
     55     (meow-leader-define-key
     56      ;; SPC j/k will run the original command in MOTION state.
     57      '("j" . "H-j")
     58      '("k" . "H-k")
     59      ;; Use SPC (0-9) for digit arguments.
     60      '("1" . meow-digit-argument)
     61      '("2" . meow-digit-argument)
     62      '("3" . meow-digit-argument)
     63      '("4" . meow-digit-argument)
     64      '("5" . meow-digit-argument)
     65      '("6" . meow-digit-argument)
     66      '("7" . meow-digit-argument)
     67      '("8" . meow-digit-argument)
     68      '("9" . meow-digit-argument)
     69      '("0" . meow-digit-argument)
     70      '("/" . meow-keypad-describe-key)
     71      '("?" . meow-cheatsheet))
     72     (meow-normal-define-key
     73      '("0" . meow-expand-0)
     74      '("9" . meow-expand-9)
     75      '("8" . meow-expand-8)
     76      '("7" . meow-expand-7)
     77      '("6" . meow-expand-6)
     78      '("5" . meow-expand-5)
     79      '("4" . meow-expand-4)
     80      '("3" . meow-expand-3)
     81      '("2" . meow-expand-2)
     82      '("1" . meow-expand-1)
     83      '("-" . negative-argument)
     84      '(";" . meow-reverse)
     85      '("," . meow-inner-of-thing)
     86      '("." . meow-bounds-of-thing)
     87      '("[" . meow-beginning-of-thing)
     88      '("]" . meow-end-of-thing)
     89      '("a" . meow-append)
     90      '("A" . meow-open-below)
     91      '("b" . meow-back-word)
     92      '("B" . meow-back-symbol)
     93      '("c" . meow-change)
     94      '("d" . meow-delete)
     95      '("D" . meow-backward-delete)
     96      '("e" . meow-next-word)
     97      '("E" . meow-next-symbol)
     98      '("f" . meow-find)
     99      '("g" . meow-cancel-selection)
    100      '("G" . meow-grab)
    101      '("h" . meow-left)
    102      '("H" . meow-left-expand)
    103      '("i" . meow-insert)
    104      '("I" . meow-open-above)
    105      '("j" . meow-next)
    106      '("J" . meow-next-expand)
    107      '("k" . meow-prev)
    108      '("K" . meow-prev-expand)
    109      '("l" . meow-right)
    110      '("L" . meow-right-expand)
    111      '("m" . meow-join)
    112      '("n" . meow-search)
    113      '("o" . meow-block)
    114      '("O" . meow-to-block)
    115      '("p" . meow-yank)
    116      '("q" . meow-quit)
    117      '("Q" . meow-goto-line)
    118      '("r" . meow-replace)
    119      '("R" . meow-swap-grab)
    120      '("s" . meow-kill)
    121      '("t" . meow-till)
    122      '("u" . meow-undo)
    123      '("U" . meow-undo-in-selection)
    124      '("v" . meow-visit)
    125      '("w" . meow-mark-word)
    126      '("W" . meow-mark-symbol)
    127      '("x" . meow-line)
    128      '("X" . meow-goto-line)
    129      '("y" . meow-save)
    130      '("Y" . meow-sync-grab)
    131      '("z" . meow-pop-selection)
    132      '("'" . repeat)
    133      '(">" . indent-rigidly-right-to-tab-stop)
    134      '("<" . indent-rigidly-left-to-tab-stop)
    135      '("<escape>" . ignore)))
    136 #+end_src
    137 :end:
    138 
    139 #+begin_src emacs-lisp
    140   (elpaca 'meow
    141     (require 'meow)
    142     (ethandl/meow-setup)
    143     (meow-global-mode 1))
    144 #+end_src
    145 Unfortunately in daemon mode, meow's fonts are not initialized correctly. Here is a workaround (very similar to [[https://github.com/meow-edit/meow/issues/491][this one on GitHub]]):
    146 #+begin_src emacs-lisp
    147   (defun ethandl/fix-meow-faces ()
    148     "Fixes the faces for meow when emacs is running in daemon mode"
    149     (add-hook 'server-after-make-frame-hook #'meow--prepare-face))
    150 #+end_src
    151 Now because we don't have any guarantee of ~meow--prepare-face~ being available until after elpaca has completed the installation of everything, we have to add this to the ~elpaca-after-init-hook~:
    152 #+begin_src emacs-lisp
    153   (add-hook 'elpaca-after-init-hook #'ethandl/fix-meow-faces)
    154 #+end_src
    155 ** Window navigation
    156 When navigating windows, I find the binding ~C-x o~ to be rather slow when you have many buffers open in different arrangements, so I have adopted a set of bindings for the ~windmove-*~ functions that are left unused in the default configuration. These binds fit in quite nicely with the ~vim~-like keybinds that I use with ~meow~.
    157 
    158 This is mostly just the work of [[https://dev.to/rajasegar/vim-style-repeatable-key-bindings-for-navigating-windows-in-emacs-5c4l][Rajasegar Chandran]].
    159 #+begin_src emacs-lisp
    160   (global-set-key (kbd "C-x w h") 'windmove-left)
    161   (global-set-key (kbd "C-x w j") 'windmove-down)
    162   (global-set-key (kbd "C-x w k") 'windmove-up)
    163   (global-set-key (kbd "C-x w l") 'windmove-right)
    164   (repeat-mode t)
    165   (defvar-keymap windmove-repeat-map
    166     :repeat t
    167     "h" #'windmove-left
    168     "j" #'windmove-down
    169     "k" #'windmove-up
    170     "l" #'windmove-right)
    171 #+end_src
    172 ** Super as meta
    173 For all systems that aren't MacOS (and even some MacOS emacs builds), the default meta key is Alt. Because I frequently use keyboards where I have swapped my meta and alt keys, I would like to keep this consistant across operating systems.
    174 
    175 The following works on Linux, it is untested on Windows, and does nothing on my MacOS build.
    176 #+begin_src emacs-lisp
    177   (setq x-super-keysym 'meta)
    178 #+end_src
    179 * Emacs appearance
    180 I think that vanilla emacs has a charming appearance that I aim to keep for the most part, I won't be using any packages to change the modeline or anything special.
    181 ** Menu bar removal
    182 The menu bar is /fine/ on MacOS or in an NS environment (see platform specific tweaks), but elsewhere it tends to add clutter.
    183 Begone.
    184 #+begin_src emacs-lisp
    185   (menu-bar-mode 0)
    186 #+end_src
    187 ** Tool bar removal
    188 Even worse than the menu bar: the tool bar.
    189 Simply a waste of space in my opinion.
    190 #+begin_src emacs-lisp
    191   (tool-bar-mode 0)
    192 #+end_src
    193 ** Scroll bar removal
    194 The scroll bar is unnecessary if you are navigating via keyboard, and takes up space.
    195 #+begin_src emacs-lisp
    196   (scroll-bar-mode 0)
    197 #+end_src
    198 ** Line numbers
    199 When working with modal editing and in general programming, it is useful to be able to see distances to previous or subsequent lines in the buffer.
    200 Emacs comes with a nice built-in mode that displays line numbers on the left-hand side of a buffer, much like in ~vi~.
    201 #+begin_src emacs-lisp
    202   (add-hook 'prog-mode-hook #'display-line-numbers-mode)
    203 #+end_src
    204 Optionally, you can also set the line numbers to be relative to the cursor position.
    205 #+begin_src emacs-lisp :tangle no
    206   (setq display-line-numbers-type 'relative)
    207 #+end_src
    208 
    209 By default, the size of the line-numbers column is only big enough for small files and will grow and shrink to accomodate the line numbers as you scroll, leading to the jarring movement of code horizontally as a buffer is scrolled.
    210 This can be fixed:
    211 #+begin_src emacs-lisp
    212   (setq display-line-numbers-width-start 0)
    213   (setq display-line-numbers-grow-only t)
    214 #+end_src
    215 ** Modeline customisation
    216 *** Disable line number
    217 The current line number is irrelevant to have on the modeline, given that we display the current line number in the buffer.
    218 #+begin_src emacs-lisp
    219   (line-number-mode 0)
    220 #+end_src
    221 *** Enable horizontal cursor position
    222 For some reason, this is not displayed by default in the Emacs modeline.
    223 It can be very useful to know the current position of the cursor in a line so that you can restrict line length in applications where that matters.
    224 #+begin_src emacs-lisp
    225   (column-number-mode 1)
    226 #+end_src
    227 ** Indentation indicators
    228 Programming languages that use indentation to control scope such as Python are greatly enhanced by the ability to see indentation more clearly.
    229 The ~indent-bars~ package by jdtsmith gives us the ability to see this indentation much more clearly.
    230 #+begin_src emacs-lisp
    231   (elpaca indent-bars
    232     (setq indent-bars-no-descend-lists 'skip) ; prevent extra bars in nested lists + skip intermediate bars
    233     (setq indent-bars-treesit-support t))
    234 #+end_src
    235 
    236 Because I want all aspects of language configuration to be in [[The programming environment][the programming environment]] section, I don't want to set language-specific variables here.
    237 The plist ~indent-bars-treesit-scope~ is one such language-specific variable, if there are ever any more that I want to configure, I will also add them here:
    238 #+begin_src emacs-lisp
    239   (defvar ethandl/indent-bars-treesit-scope nil)
    240   (defun ethandl/set-indent-bars-treesit-scope ()
    241     (require 'indent-bars)
    242     (setq indent-bars-treesit-scope ethandl/indent-bars-treesit-scope))
    243   (add-hook 'elpaca-after-init-hook #'ethandl/set-indent-bars-treesit-scope)
    244 #+end_src
    245 ** Fonts
    246 *** Code fonts
    247 These are the fonts used generally by the Emacs user interface, and in any buffer that doesn't use ~variable-pitch-mode~.
    248 
    249 I like the fonts created by Toshi Omagari, primarily Codelia and Comic code.
    250 As a fallback, I don't mind Monaco/Monego.
    251 #+begin_src emacs-lisp
    252   (defvar ethandl/font-name nil
    253     "Font name for regular Emacs fonts
    254   Used in all buffers.")
    255 
    256   (defvar ethandl/font-size nil
    257     "Size for regular Emacs fonts
    258   Used in all buffers.")
    259 
    260   (defvar ethandl/default-font-size 12
    261     "Default size for regular Emacs fonts
    262   Used in all buffers.")
    263 
    264   (defvar ethandl/font-string nil
    265     "Fully qualified string for use with `set-frame-font'
    266   Describes the font used in all regular Emacs buffers.")
    267 
    268   (defun ethandl/font-exists (fname)
    269     "Returns nil if the font FNAME does not exist, else
    270   returns the font list after and including FNAME"
    271     (member fname (font-family-list)))
    272 
    273   (defun ethandl/set-font-vars (&optional name size)
    274     "Set font configuration variables
    275 
    276   Use NAME as the font family and SIZE as the font size in
    277   points. If NAME is nil, select the first available font
    278   from a predefined list. If SIZE is nil, uses
    279   `ethandl/default-font-size'.
    280 
    281   Sets `ethandl/font-name', `ethandl/font-size', and
    282   `ethandl/font-string'."
    283     (setq ethandl/font-name
    284   	(or name
    285   	    (seq-find #'ethandl/font-exists
    286     		      '("Codelia Ligatures"
    287     			"Comic Code Ligatures"
    288     			"Monego"
    289     			"Monaco"
    290     			"monospace"))))
    291     (setq ethandl/font-size
    292     	(or size ethandl/default-font-size))
    293     (setq ethandl/font-string
    294     	(format "%s-%d" ethandl/font-name ethandl/font-size)))
    295 
    296   (defun ethandl/set-frame-font (&optional frame name size)
    297     "Sets the font for a frame or all frames if FRAME is not
    298   specified.
    299 
    300   Use FRAME to specify the frame to change, NAME as the font family,
    301   and SIZE as the font size in points. If FRAME is nil, will apply to
    302   all open frames. If NAME or SIZE are nil, follows the same
    303   convention as `ethandl/set-font-vars'."
    304     (if (or name size)
    305         (ethandl/set-font-vars name size)
    306       (unless (and ethandl/font-name
    307   		 ethandl/font-size
    308   		 ethandl/font-string)
    309         (ethandl/set-font-vars)))
    310     
    311     (set-frame-font ethandl/font-string t (if frame (list frame) t)))
    312 #+end_src
    313 Now we ensure that these functions apply the fonts both when the config is loaded, and whenever a new frame is made.
    314 It is particularly important to have the ~server-after-make-frame-hook~ if you are planning on running Emacs as a daemon:
    315 #+begin_src emacs-lisp
    316   (ethandl/set-font-vars)
    317   (ethandl/set-frame-font)
    318   
    319   (add-hook 'after-make-frame-functions #'ethandl/set-frame-font)
    320   (add-hook 'server-after-make-frame-hook #'ethandl/set-frame-font)
    321 #+end_src
    322 *** Org fonts
    323 Org documents are configured to start in variable pitch mode such that we have two sets of fonts: Body (variable-pitch), and verbatim (monospaced fixed-pitch):
    324 #+begin_src emacs-lisp
    325   (add-hook 'org-mode-hook #'variable-pitch-mode)
    326 #+end_src
    327 
    328 To define our desired fonts, we create functions as we did before in [[Code fonts]]:
    329 #+begin_src emacs-lisp
    330   (defvar ethandl/org-mono-font-name nil
    331     "Font for code and any monospace blocks in Org documents")
    332   (defvar ethandl/org-var-font-name nil
    333     "Font for Org documents, variable width")
    334   (defvar ethandl/org-default-font-height 140
    335     "Default height for fonts in org documents")
    336   (defvar ethandl/org-font-ht nil
    337     "Font height in org documents")
    338   
    339   (defun ethandl/org-set-font-vars (&optional height var mono)
    340     "Sets the mono font variables for Org mode
    341 
    342   Use HEIGHT to specify the font height in px, VAR as the
    343   variable-pitch font family, and MONO as the monospace font
    344   family. If HEIGHT is nil, uses the default height given by
    345   `ethandl/org-default-font-height'. If VAR or MONO are nil,
    346   selects the first available font for each from a predefined
    347   list."
    348     (setq ethandl/org-mono-font-name
    349   	(or mono
    350   	    (seq-find #'ethandl/font-exists
    351   		      '("Codelia Ligatures"
    352   			"Iosevka"
    353   			"Monego"
    354   			"Monaco"
    355   			"CMU Typewriter Text"
    356   			"DejaVu Sans Mono"
    357   			"monospace"))))
    358     (setq ethandl/org-var-font-name
    359   	(or var
    360   	    (seq-find #'ethandl/font-exists
    361   		      '("CMU Serif"
    362   			"Iosevka Aile"
    363   			"Times New Roman"
    364   			"DejaVu Serif"))))
    365     (setq ethandl/org-font-ht
    366   	(or height ethandl/org-default-font-height)))
    367 #+end_src
    368 
    369 Next, we define a way to easily apply these fonts.
    370 This is not as simple as it should be, because ~variable-pitch-mode~ takes over all Org fonts.
    371 #+begin_src emacs-lisp
    372   (defun ethandl/org-set-fonts (&optional height var mono)
    373     "Applies the mono fonts for org mode. Arguments are the
    374   same as for `ethandl/set-org-font-vars'."
    375     (if (or height var mono)
    376         (ethandl/org-set-font-vars height var mono)
    377       (unless (and ethandl/org-mono-font-name
    378   		 ethandl/org-var-font-name
    379   		 ethandl/org-font-ht)
    380         (ethandl/org-set-font-vars)))
    381     
    382     (custom-theme-set-faces
    383      'user
    384      `(variable-pitch ((t (:family ,ethandl/org-var-font-name :height ,ethandl/org-font-ht))))
    385      `(fixed-pitch ((t (:family ,ethandl/org-mono-font-name :height 0.9))))
    386      
    387      '(org-block ((t (:inherit fixed-pitch))))
    388      '(org-code ((t (:inherit (shadow fixed-pitch)))))
    389      '(org-document-info ((t (:foreground "dark orange"))))
    390      '(org-document-info-keyword ((t (:inherit (shadow fixed-pitch)))))
    391      '(org-indent ((t (:inherit (org-hide fixed-pitch)))))
    392      '(org-link ((t (:foreground "royal blue" :underline t))))
    393      '(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch)))))
    394      '(org-property-value ((t (:inherit fixed-pitch))) t)
    395      '(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch)))))
    396      '(org-table ((t (:inherit fixed-pitch :foreground "#83a598"))))
    397      '(org-tag ((t (:inherit (shadow fixed-pitch) :weight bold :height 0.8))))
    398      '(org-verbatim ((t (:inherit (shadow fixed-pitch)))))))
    399 #+end_src
    400 
    401 Finally, we ensure that these functions are run whenever necessary:
    402 #+begin_src emacs-lisp
    403   ;; Evaluate available fonts when loading config
    404   (ethandl/org-set-font-vars)
    405 
    406   ;; Evaluate available fonts when making a new frame (relevant for daemon mode)
    407   (add-hook 'after-make-frame-functions #'ethandl/org-set-font-vars)
    408   (add-hook 'server-after-make-frame-hook #'ethandl/org-set-font-vars)
    409 
    410   ;; Set the fonts when loading into an org buffer
    411   (add-hook 'org-mode-hook #'ethandl/org-set-fonts)
    412 #+end_src
    413 *** Ligatures
    414 In order to get ligatures to display in regular builds of emacs, we use ~ligature.el~:
    415 #+begin_src emacs-lisp
    416   (unless (eq system-type 'darwin)
    417     (elpaca 'ligature
    418       (ligature-set-ligatures 'prog-mode '("<---" "<--"  "<<-" "<-" "->" "-->" "--->" "<->" "<-->" "<--->" "<---->" "<!--"
    419   					 "<==" "<===" "<=" "=>" "=>>" "==>" "===>" ">=" "<=>" "<==>" "<===>" "<====>" "<!---"
    420   					 "<~~" "<~" "~>" "~~>" "::" ":::" "==" "!=" "===" "!=="
    421   					 ":=" ":-" ":+" "<*" "<*>" "*>" "<|" "<|>" "|>" "+:" "-:" "=:" "<******>" "++" "+++"))
    422       (global-ligature-mode t)))
    423 #+end_src
    424 ** Theme
    425 I like the Catppuccin theme, but the official one kinda stinks. As such, I use the ~batppuccin~ theme:
    426 #+begin_src emacs-lisp
    427   (elpaca 'batppuccin
    428     (require 'batppuccin)
    429     (load-theme 'batppuccin-mocha t))
    430 #+end_src
    431 
    432 Another nice theme is ~gruber-darker~ by tsoding, one can use it similarly:
    433 #+begin_src emacs-lisp :tangle no
    434   (elpaca 'gruber-darker-theme
    435     (require 'gruber-darker-theme)
    436     (load-theme 'gruber-darker t))
    437 #+end_src
    438 * Emacs behaviour
    439 ** Startup screen removal
    440 Boot straight into a scratch buffer, removing the startup screen:
    441 #+begin_src emacs-lisp
    442   (setq-default inhibit-startup-screen t)
    443 #+end_src
    444 ** Line wrapping
    445 The default line wrapping doesn't respect words and will arbitrarily wrap on the last character.
    446 Luckily Emacs comes with an alternate wrapping method:
    447 #+begin_src emacs-lisp
    448   (global-visual-line-mode 1)
    449 #+end_src
    450 
    451 Futhermore, with vanilla Emacs 30.1 or newer the indentation of the wrapped line will be respected with the addition of the following mode:
    452 #+begin_src emacs-lisp
    453   (when (fboundp 'global-visual-wrap-prefix-mode)
    454     (global-visual-wrap-prefix-mode 1))
    455 #+end_src
    456 ** Scrolling
    457 There are a collection of variables that make scrolling behave more like that in ~vi~, my poison.
    458 #+begin_src emacs-lisp
    459   (setq scroll-conservatively 10000
    460         scroll-margin 0
    461         auto-window-vscroll nil)
    462 #+end_src
    463 
    464 For smooth mouse and faster document scrolling, I use the package ~ultra-scroll~ by jdtsmith.
    465 This works on all emacs builds to my knowledge, and is by far the best scrolling package.
    466 #+begin_src emacs-lisp
    467   (elpaca 'ultra-scroll
    468     (require 'ultra-scroll)
    469     (ultra-scroll-mode 1)
    470     (add-hook 'ultra-scroll-hide-functions #'org-modern-mode))
    471 #+end_src
    472 ** Suppress ~native-compiler~ warnings
    473 These warnings are generally not useful.
    474 It seems that there's a lot that can go wrong with native compilation that cause it to freak out and give warnings, but as long as we aren't gettings errors, everything is fine.
    475 For debugging purposes, you can disable the tangling of this block:
    476 #+begin_src emacs-lisp
    477   (setq native-comp-async-report-warnings-errors `silent)
    478 #+end_src
    479 ** Asyncronous commands
    480 The package ~async~ allows for us to run long, blocking commands in separate Emacs processes.
    481 #+begin_src emacs-lisp
    482   (defvar ethandl/async-job-queue nil)
    483   (defvar ethandl/async-job-results nil)
    484 
    485   (defun ethandl/eval-async-job (job)
    486     (let ((form (car job))
    487   	(inject-vars (cadr job)))
    488       (async-start
    489        (if inject-vars
    490   	 `(lambda ()
    491   	    ,(async-inject-variables inject-vars)
    492   	    ,form)
    493          `(lambda () ,form))
    494        (lambda (result)
    495          (push result ethandl/async-job-results)))))
    496        
    497 
    498   (defun ethandl/eval-async-job-queue ()
    499     (mapc #'ethandl/eval-async-job ethandl/async-job-queue)
    500     (setq ethandl/async-job-queue nil))
    501 
    502   (defun ethandl/async-job-queue-add (form &optional inject-vars)
    503     (push (list form inject-vars) ethandl/async-job-queue))
    504 
    505   (elpaca 'async
    506     (require 'async)
    507     (ethandl/eval-async-job-queue)
    508     (run-with-idle-timer 30 t 'ethandl/eval-async-job-queue))
    509 #+end_src
    510 ** Ahead-of-time nativecomp
    511 After just installing this Emacs config, things can be a little stuttery. We can speed this up by telling Emacs to nativecomp everything.
    512 
    513 Here is a simple convenience function to compile everything in this config, including all packages installed with ~elpaca~:
    514 #+begin_src emacs-lisp
    515   (defun ethandl/nativecomp-aot ()
    516     (interactive)
    517     (native-compile-async user-emacs-directory t))
    518 #+end_src
    519 
    520 We add this to the ~elpaca-after-init-hook~ so that everything is compiled after initial installation. Any subsequent compilation can occur at runtime.
    521 #+begin_src emacs-lisp
    522   (add-hook 'elpaca-after-init-hook
    523   	  #'ethandl/nativecomp-aot)
    524 #+end_src
    525 ** Indenting without tabs
    526 Tabs make your code unreadable on editors with different tab settings if used incorrectly.
    527 Unfortunately Emacs does this incorrectly by default, so I will disable this behaviour:
    528 #+begin_src emacs-lisp
    529   (indent-tabs-mode -1)
    530   (setq-default indent-tabs-mode -1)
    531 #+end_src
    532 ** Autosave & backup directories
    533 By default, Emacs puts backup files alongside regular files in the format of ~filename~~.
    534 This clutters up directories with extra files, and can be problematic when working in Git repos or shared spaces.
    535 
    536 Emacs additionally puts autosave files in the format of ~.#filename~.
    537 Whilst these are less intrusive and disappear upon saving, it is still worth putting them in a dedicated directory.
    538 
    539 First defining our dedicated directories for both of these files:
    540 #+begin_src emacs-lisp
    541   (defvar ethandl/backup-dir
    542     (expand-file-name "backups/" user-emacs-directory))
    543   (defvar ethandl/autosave-dir
    544     (expand-file-name "autosaves/" user-emacs-directory))
    545 #+end_src
    546 Now we ensure the directories exist:
    547 #+begin_src emacs-lisp
    548   (unless (file-directory-p ethandl/backup-dir)
    549     (make-directory ethandl/backup-dir t))
    550   (unless (file-directory-p ethandl/autosave-dir)
    551     (make-directory ethandl/autosave-dir t))
    552 #+end_src
    553 Finally we set the appropriate variables so that these directories are used:
    554 #+begin_src emacs-lisp
    555   (setq backup-directory-alist `(("." . ,ethandl/backup-dir)))
    556   (setq auto-save-file-name-transforms
    557         `((".*" ,ethandl/autosave-dir t)))
    558 #+end_src
    559 * Org mode
    560 ** Section numbering
    561 I like my sections numbered:
    562 #+begin_src emacs-lisp
    563   (add-hook 'org-mode-hook #'org-num-mode)
    564 #+end_src
    565 ** Better emphasis rendering
    566 Emphasis markers (/italics/, *bold*, _underline_, ~verbatim~, =code=, and +strikethrough+) all render with the markers (~/~, ~*~, ~_~, ~~~, ~=~, and ~+~) included in the text by default.
    567 I think that these markers work better when they aren't rendered:
    568 #+begin_src emacs-lisp
    569   (setq org-hide-emphasis-markers t)
    570 #+end_src
    571 ** Pretty appearance
    572 I know that I said that I would keep things minimal...
    573 
    574 I lied: this package is just for beauty because Org is somewhat ugly by default.
    575 Unfortunately, this package conflicts with ~visual-wrap-prefix-mode~, so we need to explicitly disable that for org mode with a hook function.
    576 #+begin_src emacs-lisp
    577   (elpaca 'org-modern
    578     (require 'org-modern)
    579     (add-hook 'org-mode-hook #'org-modern-mode)
    580     (add-hook 'org-agenda-finalize-hook #'org-modern-agenda)
    581     (add-hook 'org-mode-hook (lambda () (visual-wrap-prefix-mode 0))))
    582 #+end_src
    583 ** \(\text{\LaTeX}\) rendering
    584 We are already secretly using a buggy Org branch to get \(\text{\LaTeX}\) to render correctly and automagically.
    585 This is not ideal, but vanilla Org doesn't support live \(\text{\LaTeX}\) rendering and is slow. It's a tradeoff...
    586 
    587 To enable this rendering automatically, as well as a live preview while writing:
    588 #+begin_src emacs-lisp
    589   (add-hook 'org-mode-hook #'org-latex-preview-mode)
    590   (setq org-latex-preview-mode-display-live t)
    591 #+end_src
    592 
    593 By default, ~org-latex-preview-mode~ only renders \(\text{\LaTeX}\) in the visible regions, which is problematic for documents like this that start in ~overview~ on startup.
    594 This can be fixed by rendering all \(\text{\LaTeX}\) in the buffer ahead of time when loading the buffer, this may be intensive for buffers with a lot of \(\text{\LaTeX}\), but at least this rewrite of ~org-latex-preview~ is asyncronous.
    595 #+begin_src emacs-lisp
    596   (add-hook 'org-mode-hook (lambda () (org-latex-preview 'buffer)))
    597 #+end_src
    598 
    599 By default, the \(\text{\LaTeX}\) previews are displayed with PNG for speed, but I will change this to use SVG for display sexiness:
    600 #+begin_src emacs-lisp
    601   (setq org-latex-preview-process-default 'dvisvgm)
    602 #+end_src
    603 ** Start with images
    604 By default, Org will wait until ~org-link-preview~ is run to display images. The following sets images to automatically load on file opening:
    605 #+begin_src emacs-lisp
    606   (setq org-startup-with-inline-images t)
    607 #+end_src
    608 
    609 If we want images generated by the output of ~org-babel~ to automatically render, we need to add the following to the hook:
    610 #+begin_src emacs-lisp
    611   (add-hook 'org-babel-after-execute-hook #'org-link-preview-region)
    612 #+end_src
    613 This will save a ~C-c C-x C-v~ invocation every time we want to display newly generated images.
    614 * LaTeX
    615 \(\text{\LaTeX}\) takes up a large part of my work, so I would like to have a nice \(\text{\LaTeX}\) editing environment.
    616 ** PDF Viewing
    617 Whilst Emacs can view PDFs out of the box (wow), it rasterises the PDFs to something that is quite low resolution and doesn't look the best on all displays and is quite slow.
    618 
    619 The ~pdf-tools~ package allows for much better PDF rendering:
    620 #+begin_src emacs-lisp
    621   (elpaca pdf-tools
    622     (require 'pdf-tools)
    623     (pdf-tools-install t t)
    624     (add-to-list 'pdf-tools-enabled-modes #'pdf-view-midnight-minor-mode)
    625     (add-to-list 'pdf-tools-enabled-modes #'auto-revert-mode))
    626 #+end_src
    627 * The programming environment
    628 This will be perpetually incomplete, my config cannot support /every/ language, nor do I really need it to.
    629 
    630 The selection of languages may seem odd, but they are just what I have been bothered to put the effort into to-date. If I stop using one and it breaks, I will delete it rather than fix it until I need it again.
    631 ** Org babel
    632 In addition to being a useful Markdown alternative, Org mode can also serve as a literate programming environment and (shitty) Jupyter replacement.
    633 
    634 The intended configuration for ~org-babel~ involves defining your list of languages to support all at once using the ~org-babel-do-load-languages~ function.
    635 To allow for the language list (contained in ~ethandl/org-babel-langs~) to be defined dynamically (even in environment), the following function is defined:
    636 #+begin_src emacs-lisp
    637   (setq ethandl/org-babel-langs nil)
    638   (defun ethandl/org-babel-load-langs ()
    639     (org-babel-do-load-languages
    640      'org-babel-load-languages
    641      ethandl/org-babel-langs))
    642 #+end_src
    643 This list is then evaluated dynamically every time an Org document is opened through the ~org-mode-hook~:
    644 #+begin_src emacs-lisp
    645   (add-hook 'org-mode-hook #'ethandl/org-babel-load-langs)
    646 #+end_src
    647 ** The ~vterm~ terminal emulator
    648 To make emacs a useful development environment, we need a good terminal.
    649 In addition to using separate (good) terminals, we can use the cromulent ~vterm~ package:
    650 #+begin_src emacs-lisp
    651   (elpaca 'vterm)
    652 #+end_src
    653 ** Treesitter grammar async install
    654 Treesitter grammar installation is slow and blocks up the emacs process. The following allows for asyncronous treesitter grammar installation on separate processes:
    655 #+begin_src emacs-lisp
    656   (defun ethandl/treesit-install-async (lang)
    657     (unless (treesit-language-available-p lang)
    658       (ethandl/async-job-queue-add
    659        `(treesit-install-language-grammar ',lang)
    660        "\\`treesit-language-source-alist\\'")))
    661 #+end_src
    662 ** Markdown
    663 For markdown, I will use ~markdown-ts-mode~, which utilises treesitter to provide highlighting for markdown.
    664 To use this mode, we need to install the treesitter grammar(s):
    665 #+begin_src emacs-lisp
    666   (add-to-list 'treesit-language-source-alist
    667   	     '(markdown "https://github.com/tree-sitter-grammars/tree-sitter-markdown"
    668   			"split_parser"
    669   			"tree-sitter-markdown/src"))
    670   (add-to-list 'treesit-language-source-alist
    671   	     '(markdown-inline "https://github.com/tree-sitter-grammars/tree-sitter-markdown"
    672   			       "split_parser"
    673   			       "tree-sitter-markdown-inline/src"))
    674 
    675   (ethandl/treesit-install-async 'markdown)
    676   (ethandl/treesit-install-async 'markdown-inline)
    677 #+end_src
    678 ** Emacs Lisp
    679 Not much needs to be done here.
    680 For verbosity, I will specify that we want Org babel to be able to evaluate elisp ~src~ blocks:
    681 #+begin_src emacs-lisp
    682   (add-to-list 'ethandl/org-babel-langs '(emacs-lisp . t))
    683 #+end_src
    684 ** Python
    685 There are two python modes installed by default with Emacs.
    686 From my (admittedly limited) experience, ~python-ts-mode~ tends to have better syntax highlighting due to it having a better AST.
    687 #+begin_src emacs-lisp
    688   (add-to-list 'treesit-language-source-alist
    689   	     '(python "https://github.com/tree-sitter/tree-sitter-python"))
    690 
    691   (add-to-list 'major-mode-remap-alist
    692   	     '(python-mode . python-ts-mode))
    693 
    694   (ethandl/treesit-install-async 'python)
    695 
    696   (add-to-list 'ethandl/org-babel-langs '(python . t))
    697 #+end_src
    698 
    699 For python, I like to have indentation visible with ~indent-bars~,
    700 #+begin_src emacs-lisp
    701   (add-hook 'python-mode-hook #'indent-bars-mode)
    702   (add-hook 'python-ts-mode-hook #'indent-bars-mode)
    703 #+end_src
    704 A simple configuration for treesit-python is given on the github repo for ~indent-bars~:
    705 #+begin_src emacs-lisp
    706   (setq indent-bars-treesit-ignore-blank-lines-types '("module"))
    707   (add-to-list 'ethandl/indent-bars-treesit-scope
    708   	     '(python function_definition class_definition for_statement
    709   		      if_statement with_statement while_statement))
    710 #+end_src
    711 ** Powershell
    712 I don't like or use Powershell, but seeing as I sometimes operate on Windows (and even have a sample in this config for TRAMP compatability), it /could/ be nice to have.
    713 
    714 Due to my general distaste for all things Microslop, this may disappear if I deem it too much effort to keep around. That said, here is a mode to use when working with Powershell (the slowest shell known to mankind).
    715 #+begin_src emacs-lisp
    716   (elpaca 'powershell)
    717 #+end_src
    718 * Tramp
    719 We all love tramp. Tramp is great.
    720 ** Windows remote support
    721 Disclaimer: This isn't my solution, though I got part of the way there are just so many reasons why Windows is broken and shit. I borrowed heavily on the PowerShell and ~sshw~ definition from [[https://emacs.stackexchange.com/a/85533][this stackexchange post]].
    722 
    723 Ensure first that the Windows remote has been de-crappified as much as possible, with Git and hence ~bash~ installed, and the OpenSSH Server feature enabled with the service started.
    724 Then run the following in a PowerShell prompt on the remote to enable ~bash~ as the SSH shell (if it isn't already):
    725 #+begin_src powershell
    726   New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\Git\bin\bash.exe" -PropertyType String -Force
    727   New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShellCommandOption -Value "-c" -PropertyType String -Force
    728 #+end_src
    729 
    730 Now that the remote is set up, we can use the following tramp method:
    731 #+begin_src emacs-lisp
    732   (require 'tramp)
    733   (add-to-list
    734    'tramp-methods
    735    '("sshw" (tramp-login-program "ssh")
    736      (tramp-login-args
    737       (("-l" "%u") ("-p" "%p") ("%c") ("-e" "none") ("-T")
    738        ("-o" "RemoteCommand=\"%l\"") ("%h")))
    739      (tramp-async-args (("-q"))) (tramp-remote-shell "/bin/sh")
    740      (tramp-remote-shell-login ("-l")) (tramp-remote-shell-args ("-c"))))
    741 #+end_src
    742 * Platform specifics
    743 I have structured the following sections such that each specific platform is tangled only if it is applicable.
    744 Tangled outputs are saved to ~config-TARG.el~ where ~TARG~ is a property of your platform.
    745 
    746 Any tangled outputs are automatically included in the final subsection of this section.
    747 
    748 Generally only small deviations from the main config should be put in here.
    749 This is the place to check if you're getting any strange bugs or different behaviour on different platforms.
    750 ** MacOS
    751 *** The menu bar
    752 Because the menu bar exists regardless on MacOS, I like to keep ~menu-bar-mode~ enabled.
    753 #+begin_src emacs-lisp :tangle (if (eq system-type 'darwin) "platform-config/darwin.el" "no")
    754   (menu-bar-mode 1)
    755 #+end_src
    756 ** Work PC
    757 My work PC has a lower resolution screen, so I like to have smaller fonts so that they take up less screen space.
    758 #+begin_src emacs-lisp :tangle (if (equal system-name "lobotomite") "platform-config/work-system.el" "no")
    759   (setq ethandl/org-default-font-height 110)
    760   (ethandl/org-set-font-vars)
    761 #+end_src
    762 ** Windows
    763 Windows is a very feculant platform to deal with, as almost nothing works as you would expect.
    764 
    765 Firstly, there is no ~nativecomp~, so we have to redefine the function to be a no-op or everything will break:
    766 #+begin_src emacs-lisp :tangle (if (eq system-type 'windows-nt) "platform-config/windows.el" "no")
    767   (defun ethandl/nativecomp-aot ()
    768     (interactive)
    769     (lwarn '(windows-is-shit) :debug "Nativecomp not supported on Windows NT. Use a real OS"))
    770 #+end_src
    771 ** Emacs <31
    772 There are a few assumptions that are made in this configuration that do not hold for versions of Emacs less than 31.
    773 *** ~markdown-ts-mode~
    774 ~markdown-ts-mode~ is only shipped with Emacs31 and newer, so we need to install it on older versions:
    775 #+begin_src emacs-lisp :tangle (if (< (string-to-number emacs-version) 31) "platform-config/old-emacs.el" "no")
    776   (elpaca markdown-ts-mode
    777     (require 'markdown-ts-mode)
    778     (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-ts-mode)))
    779 #+end_src
    780 ** Including the platforms
    781 Finally, we include any tangled platforms:
    782 #+begin_src emacs-lisp
    783   (dolist (file (directory-files ethandl/platform-config-dir t "\\.el$"))
    784     (load file))
    785 #+end_src