Read this document in this repository's github.io page or in Emacs for the best experience.
- Github.io page: https://fr33zing.github.io/fr33macs
- Repository: https://github.com/fr33zing/fr33macs
- flake.nix - Nix flake
- config.org - Emacs configuration
- export.scss - Org-mode HTML export styles
This document is tangled to create my Emacs configuration. If you want to try it for yourself, click here.
Mostly intended for use with Wayland on Linux, this configuration uses the pure-gtk version of Emacs 29. The build process, including fetching gathering all external dependencies, is handled by the Nix flake.
- Fetch and build external dependencies, including any developer tools needed for the languages I use, such as: compilers, libraries, formatters, linters, language servers.
- Use
sass
to convertexport.scss
into CSS. That CSS is then used to style this document when exported to HTML. - Tangle configuration from
config.org
. - Substitute variables from Nix flake (including the export CSS) into tangled configuration.
- Build Emacs with tangled and subtituted configuration.
- Wrap Emacs binary to give it exclusive access to its external dependencies.
Utility scripts are located in ~util/~ and they’re used to cover parts of the Nix flake build that wouldn’t be worth it to implement in Nix.
This file is not tangled using org-babel-table
. Instead, it’s
tangled using a bare-bones implementation in ~util/tangle.py~, and
thus no fancy tangle features are supported. The header-args
property at the top of the file is not even parsed, it’s only there to
maintain compatibility with other tangle implementations.
The function getnix
is used to get a variable from the Nix flake,
sort of like getenv
but not exactly. It isn’t actually a function
that exists. Instead, ~util/substitute.py~ is used to substitute all
instances of (getnix "variableName")
with the appropriate Elisp
literal.
Different elements, (i.e. text, blocks, headings, etc), should be separated with blank lines except between multiple headings. Omitting the blank line between multiple headings is necessary for heading visibility cycling to work as expected.
While reading this document you may see what look to be XML tags, e.g.:
(defun something-spanning-multiple-src-blocks ()
(beginning-of-the-defun))
;; <defun>
(middle-of-the-defun)
(end-of-the-defun)) ; <-- closes the defun
;; </defun>
These tags are not parsed. They are only used to indicate to the reader that code in the same scope may be spread across multiple src blocks.
Back up your .emacs.d
directory if you already have a configuration
you care about. I don’t think that this will affect it, but back it
up anyway just to be safe.
If you don’t have Nix installed, click here.
If you have the experimental features nix-command and flakes enabled:
nix run github:fr33zing/fr33macs
If you don’t have them enabled but you want to enable them, click here.
nix --experimental-features 'nix-command flakes' run github:fr33zing/fr33macs
Disable unwanted UI elements and startup messages.
(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(setq inhibit-startup-message t
use-dialog-box nil)
Enable some things which should probably be default.
(setq require-final-newline t)
(setq frame-inhibit-implied-resize t)
(global-hl-line-mode)
Enable relative line numbers globally.
(setq display-line-numbers-type 'relative)
(global-display-line-numbers-mode)
Try to always split window vertically.
(setq split-width-threshold 0)
(setq split-height-threshold nil)
(add-to-list 'custom-theme-load-path (getnix "themeDir"))
(load-theme 'catppuccin t)
(setq catppuccin-flavor (getnix "themeVariant"))
(catppuccin-reload)
(defvar my:cursor-color (catppuccin-get-color 'sapphire)
"Default cursor color.")
(defvar my:switch-window-cursor-color (catppuccin-get-color 'red)
"Cursor color when switching windows.")
(defvar my:code-color (catppuccin-get-color 'text)
"Default color for text that is considered code.")
Notice: Not all theming is done in this section.
(set-face-attribute 'default t :font (getnix "font"))
(set-cursor-color my:cursor-color)
(set-face-attribute 'region nil
:background (catppuccin-get-color 'surface2))
Dash is a library for dealing with lists.
(use-package dash :ensure t)
Map provides generic map-manipulation functions that work on alists, plists, hash-tables, and arrays.
(use-package map)
Less commonly used functions that complement basic APIs.
(use-package subr-x)
Notice: Not all functions are defined in this section. There are some less general / more context-specific functions that are scattered around with no particular rhyme or reason.
(defun my:advice:be-quiet (orig-fun &rest args)
"Advice function: Suppress messages in ORIG-FUN.
Example:
(advice-add 'svg-tag-mode-on :around #'my:advice:be-quiet)"
(let ((inhibit-message t)
(message-log-max nil))
(apply orig-fun args)))
(defun my:advice:nav-flash-show (&rest _)
"Advice function: Call `nav-flash-show' after a command.
Example:
(advice-add 'avy-goto-char :after #'my:advice:nav-flash-show)"
(nav-flash-show))
(defun my:advice:always-force-save (&rest _)
(set-buffer-modified-p t))
(advice-add 'save-buffer :before #'my:advice:always-force-save)
(defun my:advice:save-after-code-actions (&rest _)
(save-buffer))
(advice-add 'lsp-ui-sideline-apply-code-actions :after #'my:advice:save-after-code-actions)
- TODO Add
debounce-eval-immediately
(setq my:debounce-timers (make-hash-table :test 'equal))
(defun my:debounce (name secs form)
"Begin a timer for SECS seconds named NAME to delay the evaluation
of FORM, typically to prevent something expensive from running
too frequently. If a pending debounce timer exists with the same
NAME it is cancelled and replaced.
Example:
(my:debounce \"print-it\" 0.5 '(print \"it\"))"
(if-let ((old-timer (map-elt my:debounce-timers name)))
(cancel-timer old-timer))
(let* ((new-timer-fn `(progn
(setq my:debounce-timers (map-delete my:debounce-timers ,name))
(eval ,form)))
(new-timer (run-with-timer secs nil 'eval new-timer-fn)))
(map-put! my:debounce-timers name new-timer)))
This function was adapted from a StackOverflow answer by Zhro.
(defun my:delete-window-maybe-kill-buffer (&optional window)
"Delete the window.
If WINDOW is nil, delete the current window.
If no other window shows its buffer, kill the buffer too."
(interactive)
(let ((buf (window-buffer window)))
(if (> (length (window-list)) 1)
(delete-window window)
(unless (get-buffer-window buf 'visible) (kill-buffer buf))
(kill-buffer buf))))
It’s irritating to need to switch to a temporary window to close it. These functions are used to make closing temporary windows more convenient.
(defun my:temporary-window-p (&optional window)
"Decide if the window is temporary.
If WINDOW is nil, use the current window."
(let ((buffer (window-buffer window)))
(with-current-buffer buffer
(not (or (buffer-file-name buffer)
(-contains? my:not-temporary-major-modes major-mode)
(--some (-contains? my:not-temporary-minor-modes it)
local-minor-modes))))))
(defun my:delete-window (window)
"Delete WINDOW or abort if WINDOW is a minibuffer."
(if (window-minibuffer-p window)
(abort-recursive-edit)
(my:delete-window-maybe-kill-buffer window)))
(defun my:next-temporary-window ()
"Attempt to find a minibuffer first. If no minibuffer is found,
then starting at the current window, find the next temporary
window."
(interactive)
(if-let ((minibuffer (active-minibuffer-window)))
minibuffer
(-first #'my:temporary-window-p (window-list))))
(defun my:delete-next-temporary-window ()
"Starting at the current window, find the next temporary window
and delete it."
(interactive)
(if-let ((window (my:next-temporary-window)))
(my:delete-window window)))
(defun my:delete-next-temporary-window-or-kb-quit ()
"Starting at the current window, find the next temporary window
and delete it. If there is none, send keyboard-quit instead."
(interactive)
(if-let ((window (my:next-temporary-window)))
(my:delete-window window)
(keyboard-quit)))
When a window’s major or minor mode matches any in these lists, that window will never be considered temporary.
(setq my:not-temporary-major-modes
'(lisp-interaction-mode magit-status-mode))
(setq my:not-temporary-minor-modes
'(org-src-mode))
(defun my:fix-next-error ()
"Jump to the next error and open code actions"
(interactive)
(next-error)
(call-interactively 'lsp-ui-sideline-apply-code-actions)
(save-buffer))
(defun my:fix-previous-error ()
"Jump to the previous error and open code actions"
(interactive)
(previous-error)
(call-interactively 'lsp-ui-sideline-apply-code-actions)
(save-buffer))
(defmacro my:set-formatter (major-mode lang-name formatter)
(let ((hook (intern (concat (symbol-name major-mode) "-hook"))))
`(add-hook ',hook (lambda ()
(setq-local format-all-formatters '((,lang-name ,formatter)))))))
Prevent clobbering symlinks.
(setq backup-by-copying t)
Use versioned backups.
(setq version-control t)
Prevent backup files from being littered all over the place.
(setq backup-directory-alist
'(("." . "~/.emacs-backups/")))
Delete excess backup versions silently.
(setq delete-old-versions t
kept-new-versions 6
kept-old-versions 2)
Pretty-hydra is used to create action menus. I’ve chosen to use hydras as the foundation for my custom keybind scheme to improve discoverability and reduce cognitive load.
(use-package major-mode-hydra
:ensure t)
(use-package pretty-hydra
:ensure t
:config
(set-face-attribute 'hydra-face-red nil
:foreground (catppuccin-get-color 'red))
(set-face-attribute 'hydra-face-blue nil
:foreground (catppuccin-get-color 'sapphire))
(set-face-attribute 'hydra-face-pink nil
:foreground (catppuccin-get-color 'pink))
(set-face-attribute 'hydra-face-teal nil
:foreground (catppuccin-get-color 'teal))
(set-face-attribute 'hydra-face-amaranth nil
:foreground (catppuccin-get-color 'mauve))
;; <config>
Notice: Not all hydras are defined in this section. Other sections that define hydra(s):
(pretty-hydra-define hydra:code
(:exit t :idle 0.25)
("Action"
(("." lsp-ui-sideline-apply-code-actions "Code actions at point")
("o" lsp-organize-imports "Organize imports")
("r" lsp-rename "Rename symbol"))
"Find"
(("n" next-error "Next error" :exit nil)
("p" previous-error "Previous error" :exit nil)
("d" lsp-ui-peek-find-definitions "Definitions")
("R" lsp-ui-peek-find-references "References"))))
(pretty-hydra-define hydra:file
(:exit t :idle 0.25)
("Switch"
(("f" find-file "Find file")
("p" project-find-file "Find file in project"))))
(defun my:last-buffer ()
(interactive)
(switch-to-buffer (other-buffer (current-buffer) 1)))
(pretty-hydra-define hydra:buffer
(:exit t :idle 0.25)
("Switch"
(("b" bufler-switch-buffer "Quick switch")
("i" bufler-list "List buffers")
("]" next-buffer "Next buffer" :exit nil)
("[" previous-buffer "Previous buffer" :exit nil)
("l" my:last-buffer "Last buffer"))
"Actions"
(("s" save-buffer "Save buffer")
("k" kill-current-buffer "Kill buffer")
("K" kill-buffer "Kill other buffer")
("r" revert-buffer "Revert current buffer")
("R" rename-buffer "Rename current buffer"))))
(pretty-hydra-define hydra:window
(:exit t :idle 0.25)
("Switch"
(("w" my:switch-window "Quick switch"))
"Actions"
(("k" delete-window "Kill window")
("K" switch-window-then-delete "Kill other window")
("s" evil-window-new "Split window horizontally")
("v" evil-window-vnew "Split window vertically"))))
(pretty-hydra-define hydra:toggle
(:exit t :idle 0.25)
("Toggle"
(("w" whitespace-mode "Whitespace visualization")
("f" format-all-mode "Format on save")
("h" lsp-inlay-hints-mode "Inlay hints"))))
(pretty-hydra-define hydra:leader
(:exit t :idle 0.25)
("Hydra"
(("m" major-mode-hydra "Mode-specific")
("t" hydra:toggle/body "Toggle")
("c" hydra:code/body "Code")
("f" hydra:file/body "File")
("b" hydra:buffer/body "Buffer")
("w" hydra:window/body "Window")
("a" hydra:avy/body "Avy")
("g" hydra:git/body "Git"))
"Shortcut"
(("SPC" project-find-file "Find file in project")
("." lsp-ui-sideline-apply-code-actions "Code actions at point")
(">" next-error "Next error")
("<" previous-error "Previous error")
("s" save-buffer "Save buffer")
("q" save-buffer-kill-emacs "Quit")))))
;; </config>
General provides a convenient way to bind keys.
(use-package general
:ensure t
:init
(setq general-override-states
'(insert emacs hybrid normal visual motion operator replace))
:config
;; <config>
Use escape to close temporary windows.
(general-define-key
:keymaps 'minibuffer-mode-map
(kbd "<escape>") 'abort-minibuffers)
(general-define-key
:states '(normal)
:keymaps 'override
(kbd "<escape>") 'my:delete-next-temporary-window)
Use spacebar to open the Leader key hydra.
(general-define-key
:states '(normal visual motion)
:keymaps 'override
"SPC" 'hydra:leader/body))
;; </config>
Which-key shows all the possible completions of a partially-input keybind.
(use-package which-key
:ensure t
:delight
:config
(which-key-mode))
Evil (extensible vi layer) emulates the main features of Vim.
(use-package evil
:ensure t
:init
(setq evil-want-keybinding nil
evil-want-C-u-scroll t
evil-undo-system 'undo-redo)
:config (evil-mode 1))
Evil collection is a collection of Evil bindings for the parts of Emacs that Evil does not cover properly by default.
(use-package evil-collection
:ensure t
:after evil
:custom
(evil-collection-want-unimpaired-p nil)
:init
(evil-collection-init))
The orderless completion style divides the pattern into space-separated components and matches candidates that match all of the components in any order.
(use-package orderless
:ensure t
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles partial-completion)))))
(use-package yasnippet
:ensure t
:delight yas-minor-mode
:hook ((lsp-mode . yas-minor-mode)))
(use-package company
:ensure t
:delight
:custom
(company-idle-delay 0.1))
Vertico is a minibuffer completion interface.
(use-package vertico
:ensure t
:init
(vertico-mode)
:config
(setq vertico-cycle t)
:bind (:map vertico-map
("C-j" . vertico-next)
("C-k" . vertico-previous)
("TAB" . vertico-insert)))
Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
:init
(savehist-mode))
Do not allow the cursor in the minibuffer prompt.
(use-package emacs
:init
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
;; <init>
Hide commands in M-x which do not work in the current mode. Vertico commands are hidden in normal buffers.
(setq read-extended-command-predicate
#'command-completion-default-include-p))
;; </init>
Marginalia adds helpful information to entries in Vertico. For
example, if you type M-x
, Vertico will open a list of usable
commands. Marginalia will add each command’s description next to it.
(use-package marginalia
:ensure t
:init
(marginalia-mode))
(use-package mini-modeline
:ensure t
:delight mini-modeline-mode
:init
(mini-modeline-mode t)
:custom
(mini-modeline-enhance-visual t)
(mini-modeline-right-padding 6))
(use-package delight
:ensure t)
(use-package autorevert
:delight auto-revert-mode)
Avy is a convenient tool to jump around and perform some actions based on a short filter.
(use-package avy
:ensure t
:after evil
:config
(setq avy-timeout-seconds 0.375)
(advice-add 'avy-process :after #'my:advice:nav-flash-show))
(pretty-hydra-define hydra:avy
(:exit t :idle 0.25)
("Filter"
(("a" avy-goto-char-timer "Char(s) with timer")
("c" avy-goto-char-in-line "Char in line")
("w" avy-goto-word-1 "Word by first char")
("s" avy-goto-symbol-1 "Symbol by first char")
("h" avy-org-goto-heading-timer "Org heading with timer"))
"Repeat"
((";" avy-next "Next match" :exit nil)
("," avy-previous "Previous match" :exit nil)
("p" avy-pop-mark "Pop mark" :exit nil))
"Actions"
(("y" avy-copy-line "Copy line")
("Y" avy-copy-region "Copy region")
("m" avy-move-line "Move line")
("M" avy-move-region "Move region")
("d" avy-kill-ring-save-whole-line "Delete line")
("D" avy-kill-ring-save-region "Delete region"))))
Nav-flash makes it easier to track the cursor across large movements by flashing the current line.
(use-package nav-flash
:ensure t
:config
(setq nav-flash-delay 0.125)
(set-face-attribute 'nav-flash-face nil
:background (face-attribute 'cursor :background)
:foreground (face-attribute 'default :background))
(--map (advice-add it :after #'my:advice:nav-flash-show)
'(evil-scroll-up evil-scroll-down)))
(use-package magit :ensure t
:init
(with-eval-after-load 'magit-mode
(add-hook 'after-save-hook 'magit-after-save-refresh-status t))
:config
(setq git-commit-style-convention-checks
'(non-empty-second-line overlong-summary-line)))
(pretty-hydra-define hydra:git
(:exit t :idle 0.25)
("Git"
(("g" magit "Open Magit"))))
(use-package markdown-mode)
(defun my:open-docs ()
(call-interactively 'eldoc)
(with-current-buffer (eldoc-doc-buffer)
(markdown-mode)))
(use-package eldoc
:after evil
:delight
:custom
(global-eldoc-mode t)
(eldoc-echo-area-use-multiline-p nil)
(evil-lookup-func 'my:open-docs))
Try allows you to try Emacs packages without installing them.
(use-package try :ensure t)
Switch-window provides a nice way to choose which window to switch to when there are more than two windows. When a switch-window command is used, a number character appears in the corner of each window. Typing one of the characters then switches focus to that window.
(use-package switch-window
:ensure t
:init
:config
(defun my:switch-window ()
"Switch window and change cursor color."
(interactive)
(set-cursor-color my:switch-window-cursor-color)
(switch-window))
(defun my:switch-window-finish-hook ()
(set-cursor-color my:cursor-color))
(add-hook 'switch-window-finish-hook #'my:switch-window-finish-hook)
(setq switch-window-input-style 'minibuffer)
(set-face-attribute 'switch-window-label nil :height 5.0))
(defun my:advice:always-show-switch-window-overlay (orig-fun &rest args)
(setq switch-window-threshold 1)
(apply orig-fun args)
(setq switch-window-threshold 2))
(advice-add 'switch-window-then-delete :around #'my:advice:always-show-switch-window-overlay)
Solaire mode helps to visually distinguish “real” buffers (code buffers) from “unreal” buffers (popups, sidebars, log buffers, terminals, etc) by giving the latter a different background.
(use-package solaire-mode
:ensure t
:after general
:config
(solaire-global-mode +1))
Bufler is an ibuffer alternative.
(use-package bufler
:ensure t
:after evil
:config
(evil-define-key 'motion bufler-list-mode-map
(kbd "RET") 'bufler-list-buffer-switch
(kbd "SPC") 'bufler-list-buffer-peek
(kbd "C-s") 'bufler-list-buffer-save
"d" 'bufler-list-buffer-kill
"x" 'bufler-list-buffer-kill))
SVG tag mode replaces keywords or regular expression with SVG tags. It
can be used as an extra-fancy alternative to hl-todo
.
(use-package svg-tag-mode
:ensure t
:config
;; <config>
Define some simple SVG tags. The regular expressions are case sensitive, so there’s going to be some redundant patterns.
(defconst my:svg-tags '(
("TODO" . sapphire) ; TODO
("WIP" . teal) ; WIP
("DONE" . green) ; DONE
("TEMP" . red) ; TEMP
("\\(Example:?\\)" . blue) ; Example Example:
("\\(HACK:?\\)" . mauve) ; HACK HACK:
("\\(Hack:?\\)" . mauve) ; Hack Hack:
("\\(IMPORTANT[:!]?\\)" . peach) ; IMPORTANT IMPORTANT! IMPORTANT:
("\\(Important[:!]?\\)" . peach) ; Important Important! Important:
("WARN" . yellow) ; WARN
("\\(Warning[:!]?\\)" . yellow))) ; Warning Warning! Warning:
Define the default style. It’s necessary to use
(getnix "tagsFontFamily")
instead of (getnix "fontFamily")
because my default
font causes some strange kerning issues.
(setq svg-lib-style-default `(
:background ,(catppuccin-get-color 'text)
:foreground ,(catppuccin-get-color 'base)
:font-family (getnix "tagsFontFamily")
:font-size (getnix "fontSize")
:font-weight 800
:padding 1
:margin 0
:stroke 0
:radius 5.5
:alignment 0.5
:width 20
:height 1.0
:scale 1.0
:ascent center
:collection "material"))
Create the tags by mapping the previously defined alist of patterns and color.
(setq svg-tag-tags (--map
`(,(car it) . ((lambda (tag) (svg-lib-tag tag svg-lib-style-default
:background ,(catppuccin-get-color (cdr it))))))
my:svg-tags))
Add advice to suppress the constant “SVG tag mode on” messages.
(advice-add 'svg-tag-mode-on :around #'my:advice:be-quiet)
Finally, enable the SVG tags globally.
(global-svg-tag-mode))
;; </config>
https://github.com/rougier/svg-tag-mode/blob/main/examples/example-2.el
(use-package format-all
:ensure t
:delight
:hook (prog-mode . format-all-mode)
:config
(setq format-all-show-errors 'never))
(use-package lsp-mode
:ensure t
:delight
:after eldoc
:hook ((lsp-mode . lsp-diagnostics-mode)
(lsp-mode . lsp-enable-which-key-integration))
:custom
(lsp-enable-on-type-formatting nil)
(lsp-auto-execute-action nil)
(lsp-headerline-breadcrumb-enable nil)
(lsp-lens-enable nil)
(lsp-diagnostics-provider :flycheck)
(lsp-keep-workspace-alive nil)
(lsp-enable-xref t)
(lsp-auto-configure t)
(lsp-eldoc-enable-hover t)
(lsp-eldoc-render-all t)
(lsp-enable-symbol-highlighting t))
(use-package lsp-ui
:ensure t
:after (lsp-mode evil)
:bind (("C-K" . lsp-ui-doc-focus-frame))
:custom
(lsp-ui-peek-enable t)
(lsp-ui-sideline-show-hover nil)
(lsp-ui-sideline-show-code-actions t)
(lsp-ui-doc-enable nil)
(lsp-ui-doc-include-signature t)
(lsp-ui-doc-show-with-cursor nil)
(lsp-ui-doc-show-with-mouse nil)
(lsp-ui-doc-position 'at-point)
(lsp-ui-doc-max-height 30))
Flycheck is a modern on-the-fly syntax checking extension.
(use-package flycheck
:ensure t
:init
(global-flycheck-mode)
(set-face-attribute 'flycheck-info nil
:underline `(:color ,(catppuccin-get-color 'green) :style wave)
:weight 'bold)
(set-face-attribute 'flycheck-error nil
:underline `(:color ,(catppuccin-get-color 'red) :style wave)
:weight 'bold)
(set-face-attribute 'flycheck-warning nil
:underline `(:color ,(catppuccin-get-color 'peach) :style wave)
:weight 'bold))
Flycheck-inline implements a minor-mode for displaying errors from Flycheck right below their reporting location
;; (use-package flycheck-inline
;; :ensure t
;; :after flycheck
;; :hook (flycheck-mode . flycheck-inline-mode))
;; (use-package flycheck-eglot
;; :ensure t
;; :after (flycheck eglot)
;; :config
;; (global-flycheck-eglot-mode 1))
Treesit-auto provides an easy way to automatically install and use tree-sitter major modes.
(use-package treesit-auto
:ensure t
:config
(setq treesit-auto-install t)
(global-treesit-auto-mode))
(use-package rainbow-delimiters
:ensure t
:init (rainbow-delimiters-mode))
(use-package dimmer
:ensure t
:init
(dimmer-configure-which-key)
(dimmer-configure-company-box)
(dimmer-configure-hydra)
(dimmer-configure-magit)
(dimmer-configure-org)
(dimmer-mode t))
Centers each buffer horizontally.
(use-package sublimity
:ensure t
:init (sublimity-mode 1))
(use-package sublimity-attractive
:custom
(sublimity-attractive-centering-width 110))
(use-package langtool
:ensure t
:init
(setq langtool-bin (executable-find "languagetool-commandline"))
:config
(add-hook 'langtool-error-exists-hook 'my:remove-langtool-overlays))
(setq my:code-properties '(font-lock-fontified
src-block))
(defun my:has-code-property-p (pos)
(--any? (get-text-property pos it) my:code-properties))
(defun my:remove-langtool-overlays-p (overlay)
(let* ((pos (overlay-start overlay))
(faces (get-text-property pos 'face)))
(my:has-code-property-p pos)
))
(defun my:remove-langtool-overlays ()
(-each (langtool--overlays-region (buffer-end -1) (buffer-end +1))
(lambda (overlay)
(when (my:remove-langtool-overlays-p overlay)
(delete-overlay overlay)))))
(use-package elisp-def :ensure t)
(use-package highlight-quoted
:ensure t
:config
(add-hook 'emacs-lisp-mode-hook 'highlight-quoted-mode))
Org is like Markdown but a lot better.
(use-package htmlize :ensure t)
(use-package org
:ensure t
:after evil htmlize
:config
;; <config>
Append custom css
to make exporting from org
into html
match our
editor theme.
(setq org-html-head
(concat "<style>" (getnix "exportCSS") "</style>"))
Change the default appearance of org documents.
(setq org-indent-mode-turns-on-hiding-stars nil
org-startup-indented t
org-startup-folded 'content)
(set-face-attribute 'org-block nil
:foreground my:code-color))
;; </config>
Fix odd indentation behavior in src blocks.
(setq org-src-preserve-indentation t
org-edit-src-content-indentation 0)
Change the html
export preamble and postamble.
- TODO Figure out how to hide the title for html exports only.
(setq org-export-with-title nil)
(setq org-html-preamble-format
'(("en" "<h1 class=\"title\">%t</h1>\n<p class=\"author\">by %a</p>")))
(setq org-html-postamble t
org-html-postamble-format '(("en" "<p class=\"date\">Export time: %T</p>")))
(setq org-todo-keywords
'((sequence "TODO" "WIP" "|" "DONE")))
(defun my:org-toggle-emphasis-markers ()
"Toggle emphasis markers [*/_=~+]"
(interactive)
(setq org-hide-emphasis-markers (not org-hide-emphasis-markers)))
(major-mode-hydra-define org-mode
(:exit t :idle 0.25 :quit-key "<escape>")
("Toggle"
(("l" org-toggle-link-display "Link display")
("E" my:org-toggle-emphasis-markers "Emphasis markers")
("c" org-toggle-checkbox "Checkbox")
("i" org-toggle-inline-images "Inline images")
("n" org-toggle-narrow-to-subtree "Narrow to subtree")
("p" org-toggle-pretty-entities "Pretty entities"))
"Insert"
(("L" org-insert-link "Link")
("s" org-store-link "(Store link to here)")
("S" org-insert-last-stored-link "Last stored link")
("t" org-time-stamp "Timestamp")
("d" org-insert-drawer "Drawer")
("T" org-table-create "Table"))
"Table"
(("a" org-table-align "Align")
("R" org-table-insert-row "Insert row" :exit nil)
("C" org-table-insert-column "Insert column" :exit nil)
("k" org-table-kill-row "Kill row")
("K" org-table-kill-column "Kill column"))
"Hydra"
(("e" hydra:org-emphasize/body "Emphasize"))))
(pretty-hydra-define hydra:org-emphasize
(:exit t :idle 0.25)
("Mnemonic"
(("x" (org-emphasize ?\s) "Clear")
("b" (org-emphasize ?*) "Bold")
("i" (org-emphasize ?/) "Italic")
("u" (org-emphasize ?_) "Underlined")
("v" (org-emphasize ?=) "Verbatim")
("c" (org-emphasize ?~) "Code")
("s" (org-emphasize ?+) "Strike-through"))
"Literal"
(("*" (org-emphasize ?*) "Bold")
("/" (org-emphasize ?/) "Italic")
("_" (org-emphasize ?_) "Underlined")
("=" (org-emphasize ?=) "Verbatim")
("~" (org-emphasize ?~) "Code")
("+" (org-emphasize ?+) "Strike-through"))))
(use-package nix-mode
:ensure t
:mode "\\.nix\\'"
:hook (nix-mode . my:nix-mode-hook))
(defun my:nix-mode-hook ()
(setq-local format-all-formatters '(("Nix" nixfmt))))
Syntax checking and linting is provided by Shellcheck via flycheck.
(add-hook 'sh-mode-hook 'flycheck-mode)
(setq bash-ts-mode-hook sh-mode-hook)
(use-package fish-mode
:ensure t
:hook (fish-mode . flycheck-mode))
(my:set-formatter python-mode "Python" black)
(setq python-ts-mode-hook python-mode-hook)
- TODO Check if rustic can use rust-ts-mode yet: brotzeit/rustic#475
- TODO Check if catppuccin-reload can be removed: catppuccin/emacs#121
(use-package project)
(use-package rustic
:ensure t
:custom
(rustic-rustfmt-config-alist '(("edition" . "2021")))
(rustic-format-trigger 'on-save)
(rustic-format-on-save-method #'my:rust-format-on-save)
(add-hook 'server-after-make-frame-hook #'catppuccin-reload)
(lsp-inlay-hint-enable nil)
(lsp-rust-analyzer-completion-add-call-argument-snippets nil)
(lsp-rust-analyzer-completion-add-call-parenthesis nil)
(lsp-rust-analyzer-display-lifetime-elision-hints-enable "skip_trivial")
(lsp-rust-analyzer-display-chaining-hints t)
(lsp-rust-analyzer-display-lifetime-elision-hints-use-parameter-names t)
(lsp-rust-analyzer-display-closure-return-type-hints t)
(lsp-rust-analyzer-display-parameter-hints t)
(lsp-rust-analyzer-display-reborrow-hints t))
For some unknown reason, format-all-mode
does not work with
rustic. Setting rustic-format-trigger
enables formatting on save,
but it needs to check for format-all-mode
in order to respect the
“Format on save” option in the Toggle hydra.
(defun my:rust-format-on-save ()
"Format the file, respecting format-all-mode"
(if format-all-mode
(rustic-format-file)))
(major-mode-hydra-define rustic-mode
(:exit t :idle 0.25 :quit-key "<escape>")
("Cargo"
(("c" rustic-cargo-check "Check")
("m" rustic-cargo-add-missing-dependencies "Add missing dependencies")
("a" rustic-cargo-add "Add dependency")
("r" rustic-cargo-rm "Remove dependency"))))
(use-package wgsl-mode
:ensure t)