#+TITLE: Chris's Personal Emacs Config #+AUTHOR: Chris Cochrun From: Chris Cochrun Date: Sat, 27 Apr 2024 22:36:33 -0500 * Table of Contents :toc: - [[#init][Init]] - [[#startup-performance][Startup Performance]] - [[#set-basic-ui-config][Set basic UI config]] - [[#lets-bootstrap-straightel][Let's bootstrap straight.el]] - [[#other-ui][Other UI]] - [[#fix-nixos][Fix NixOS]] - [[#spell-check][Spell Check]] - [[#proced][Proced]] - [[#keep-folders-clean][Keep Folders Clean]] - [[#ligatures][Ligatures]] - [[#keybindings][Keybindings]] - [[#bluetooth][Bluetooth]] - [[#org-mode][Org Mode]] - [[#frame-commands][Frame Commands]] - [[#ai][AI]] - [[#jinx][Jinx]] - [[#emoji][Emoji]] - [[#undo-tree][Undo-Tree]] - [[#undo-fu][Undo-Fu]] - [[#better-ui][Better UI]] - [[#eww][EWW]] - [[#completion][Completion]] - [[#devdocs][Devdocs]] - [[#yasnippet][YASnippet]] - [[#tempel][Tempel]] - [[#projectile][Projectile]] - [[#project][Project]] - [[#gitlab][Gitlab]] - [[#httpd][HTTPD]] - [[#navigation][Navigation]] - [[#window-management][Window Management]] - [[#help][Help]] - [[#format][Format]] - [[#languages][Languages]] - [[#direnv][direnv]] - [[#file-management][File Management]] - [[#ledger][Ledger]] - [[#mu4e][MU4E]] - [[#org-caldav-sync][Org-Caldav-sync]] - [[#org-notifications][Org Notifications]] - [[#magit][Magit]] - [[#eshell][Eshell]] - [[#vterm][Vterm]] - [[#pdf-tools][PDF-Tools]] - [[#epub][EPUB]] - [[#eaf-emacs-application-framework][EAF (Emacs Application Framework)]] - [[#empv][EMPV]] - [[#elfeed][Elfeed]] - [[#bongo][Bongo]] - [[#emms][EMMS]] - [[#transmission][Transmission]] - [[#hass][HASS]] - [[#pass][Pass]] - [[#matrixement][Matrix/Ement]] - [[#mastodon][Mastodon]] - [[#activitywatch][ActivityWatch]] - [[#languagetool][LanguageTool]] - [[#qrencode][qrencode]] - [[#org-bible][Org Bible]] - [[#emacs-as-rpg][Emacs as RPG]] - [[#performance][Performance]] - [[#logging][Logging]] - [[#early-init][Early Init]] * Init :PROPERTIES: :header-args: emacs-lisp :tangle init.el :END: This init section tangles out to =init.el=. We'll do most of our configuring here. ** Startup Performance Let's create a message that will tell us how long it takes emacs to load in order to keep an eye on performance. #+begin_src emacs-lisp ;;; init.el -*- lexical-binding: t; -*- (defun chris/display-startup-time () (message "Emacs loaded in %s with %d garbage collections." (format "%.2f seconds" (float-time (time-subtract after-init-time before-init-time))) gcs-done)) (add-hook 'emacs-startup-hook #'chris/display-startup-time) #+end_src Let's also set the =gc-cons-threshold= variable to a high setting for the remainder of our setup process to speed things up. This is no long done here but instead is done in the =early-init.el= file. #+begin_src emacs-lisp :tangle no (setq gc-cons-threshold 50000000) #+end_src ** Set basic UI config Let's start by making some basic ui changes like turning off the scrollbar, toolbar, menu, tooltips, and setting our font and some things. #+begin_src emacs-lisp (setq inhibit-startup-message t) (setq default-frame-alist '((undecorated . t))) (tool-bar-mode -1) (tooltip-mode -1) (set-fringe-mode +1) (scroll-bar-mode -1) (menu-bar-mode -1) (blink-cursor-mode -1) (column-number-mode +1) (setq-default indent-tabs-mode nil) (setq comp-deferred-compilation-deny-list nil) (setq frame-resize-pixelwise t) (set-frame-parameter nil 'undecorated t) #+end_src In order to have this config work on both my desktop with regular joe-schmoe monitors and my laptop with new-hotness HiDPI monitor, I will set the font size if my system is the laptop to much higher. #+begin_src emacs-lisp (if (string-equal (system-name) "syl") (defvar chris/default-font-size 120) (defvar chris/default-font-size 120)) (defun chris/set-font-faces () "Set the faces for our fonts" (interactive) (message "Setting faces!") (set-face-attribute 'default nil :font "VictorMono Nerd Font Propo" :height chris/default-font-size) (set-face-attribute 'fixed-pitch nil :font "VictorMono Nerd Font Propo" :height chris/default-font-size) (set-face-attribute 'variable-pitch nil :font "Noto Sans" :height (+ chris/default-font-size (/ chris/default-font-size 12)) :weight 'regular)) (defun chris/set-transparency () "Set the frame to be transparent but not the text" (set-frame-parameter (selected-frame) 'alpha-background 85) (add-to-list 'default-frame-alist '(alpha-background . 85)) (add-to-list 'initial-frame-alist '(alpha-background . 85))) (if (daemonp) (add-hook 'after-make-frame-functions (lambda (frame) (with-selected-frame frame (chris/set-font-faces) (chris/set-transparency) (tool-bar-mode -1))) (chris/set-font-faces))) #+end_src Then let's make sure line-numbers are relative and on. And let's turn on visual-line-mode globally. #+begin_src emacs-lisp (setq display-line-numbers-type t) (global-display-line-numbers-mode +1) (add-hook 'prog-mode-hook (display-line-numbers-mode +1)) (global-visual-line-mode +1) #+end_src I'm adding a terminal to my workflow because sometimes that's better for me. #+begin_src emacs-lisp (defun on-frame-open (frame) (if (not (display-graphic-p frame)) (set-face-background 'default "unspecified-bg" frame))) (add-hook 'after-make-frame-functions 'on-frame-open) #+end_src Here are some ui changes I pulled from Doom Emacs #+begin_src emacs-lisp ;; always avoid GUI (setq use-dialog-box nil) ;; Don't display floating tooltips; display their contents in the echo-area, ;; because native tooltips are ugly. (when (bound-and-true-p tooltip-mode) (tooltip-mode -1)) ;; ...especially on linux (setq x-gtk-use-system-tooltips nil) ;; Favor vertical splits over horizontal ones. Screens are usually wide. (setq split-width-threshold 160 split-height-threshold nil) #+end_src Let's make doc-view better #+begin_src emacs-lisp (setq doc-view-resolution 192) #+end_src I need to fix evil-org and these seems about good as place as any to fix it. #+BEGIN_SRC emacs-lisp (fset 'evil-redirect-digit-argument 'ignore) #+END_SRC Also, real quick let's make sure that ~~ works as the same as ~~ #+begin_src emacs-lisp (global-set-key (kbd "") 'keyboard-escape-quit) #+end_src Let's also turn on =recentf-mode=. #+begin_src emacs-lisp (recentf-mode +1) ;; (savehist-mode +1) ;; (add-to-list 'savehist-additional-variables 'register-alist) ;; (add-to-list 'savehist-additional-variables kill-ring) #+end_src Finally this is not part of the UI but let's do this early and start the server so I can use emacsclient from my WM. #+begin_src emacs-lisp :tangle no (server-start) #+end_src I will also add my personal scripts to emacs' PATH #+begin_src emacs-lisp (add-to-list 'exec-path "/home/chris/bin") (add-to-list 'exec-path "/home/chris/.cargo/bin") #+end_src Let's also set org-mode as the scratch buffer mode #+begin_src emacs-lisp (setq initial-major-mode 'org-mode) (setq initial-scratch-message "#+TITLE: SCRATCH\n#+DESCRIPTION: This buffer is for temporary things\n") #+end_src This is to make sure Emacs doesn't add a newline in files that I don't want it to. A lot of major modes change this which is fine since this mostly only applies to some small specific files that mostly only open in =fundamental-mode=. #+begin_src emacs-lisp (setq require-final-newline nil) #+end_src ** Let's bootstrap straight.el :PROPERTIES: :ID: 20230524T150405.435338 :END: Before bootstrapping straight, I've begun to move more and more of my configurations into Nix. To ensure that the packages used by Nix are loaded and straight defers to that, let's make sure they are found in the load path first. #+begin_src emacs-lisp (dolist (path load-path) (when (string-match-p "/nix/store/[a-z0-9]\\{32\\}-emacs-packages-deps.*" path) (dolist (autoload-file (directory-files path t "-autoloads.el")) (with-demoted-errors "init.el error: %s" (load autoload-file nil t))))) #+end_src To use straight we need to bootstrap it. This code is pulled right from Straight's github repo. #+begin_src emacs-lisp :tangle no (setq straight-fix-org t) (setq straight-check-for-modifications '(check-on-save find-when-checking)) (defvar bootstrap-version) (let ((bootstrap-file (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory)) (bootstrap-version 5)) (unless (file-exists-p bootstrap-file) (with-current-buffer (url-retrieve-synchronously "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el" 'silent 'inhibit-cookies) (goto-char (point-max)) (eval-print-last-sexp))) (load bootstrap-file nil 'nomessage)) (setq straight-use-package-by-default t) (straight-use-package 'use-package) #+end_src In order to make sure I can use melpa packages without straight... #+begin_src emacs-lisp (require 'package) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t) (package-initialize) #+end_src Now, let's turn on =use-package=. #+begin_src emacs-lisp (eval-when-compile (require 'use-package)) #+end_src #+begin_src emacs-lisp (setq use-package-verbose t) (defalias 'yes-or-no-p 'y-or-n-p) #+end_src Now let's make sure our package archives includes the newer org. #+begin_src emacs-lisp :tangle no (add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/") t) #+end_src Command-log-mode #+begin_src emacs-lisp (use-package command-log-mode :commands command-log-mode) #+end_src This bit of code reloads emacs packages after updating nix. #+begin_src emacs-lisp (defun chris/home-manager-reload () (interactive) (let* ((which-emacs (shell-command-to-string "nix-store --query $(which emacs)")) (pattern (rx "newLoadPath+=(/nix/store/" (group (* any)) "-emacs-packages-deps/share/emacs/site-lisp)")) (current-deps (when (string-match pattern which-emacs) (match-string 1 which-emacs))) (subdirs (concat "/nix/store/" current-deps "-emacs-packages-deps/share/emacs/site-lisp/subdirs.el")) (native (concat "/nix/store/" current-deps "-emacs-packages-deps/share/emacs/native-lisp/"))) (load-file subdirs) (when (boundp 'native-comp-eln-load-path) (add-to-list 'native-comp-eln-load-path native)))) #+end_src ** Other UI All the icons is super pretty and needed for doom-modeline. #+begin_src emacs-lisp (use-package all-the-icons) #+end_src Probably the prettiest and best modeline I've found. #+begin_src emacs-lisp (use-package doom-modeline :init (doom-modeline-mode 0) (setq doom-modeline-height 25 doom-modeline-bar-width 3 all-the-icons-scale-factor 0.9 doom-modeline-hud nil doom-modeline-buffer-file-name-style 'file-name doom-modeline-buffer-encoding nil doom-modeline-mu4e t doom-modeline-enable-word-count nil) (if (daemonp) (add-hook 'after-make-frame-functions (lambda (frame) (with-selected-frame frame (setq doom-modeline-icon t)))))) #+end_src #+begin_src emacs-lisp :tangle no (use-package nano-modeline :config (setq nano-modeline-space-top 0.25 nano-modeline-space-bottom -0.25 nano-modeline-prefix 'icon)) #+end_src Again, doom is pretty and I have fallen in love with the snazzy theme and use it about anywhere I can. #+begin_src emacs-lisp (use-package doom-themes :ensure t :init (load-theme 'doom-snazzy t)) #+end_src Let's make parens and other delimiters easier to tell apart by making nested ones different colors. #+begin_src emacs-lisp (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) #+end_src #+begin_src emacs-lisp (use-package aggressive-indent :config (aggressive-indent-mode -1)) #+end_src #+begin_src emacs-lisp (use-package adaptive-wrap) #+end_src #+begin_src emacs-lisp (use-package which-key :config (setq which-key-idle-delay 0.3) (which-key-mode)) #+end_src *** Mini Echo #+begin_src emacs-lisp (use-package mini-echo :init (mini-echo-mode 1) (setq mini-echo-default-segments '(:long ("evil" "major-mode" "buffer-name" "vcs" "buffer-position" "buffer-size" "flymake" "process" "selection-info" "narrow" "macro" "profiler") :short ("buffer-name-short" "buffer-position" "process" "profiler" "selection-info" "narrow" "macro")) mini-echo-rules '((org-mode :long (("evil" . 1) ("major-mode" . 2) ("buffer-name" . 3) ("word-count" . 4)))) mini-echo-right-padding 2 mini-echo-buffer-status-style 'both mini-echo-update-interval 0.2) (set-face-foreground 'mini-echo-blue "#57c7ff") (set-face-foreground 'mini-echo-red "#ff5c57") (set-face-foreground 'mini-echo-magenta "#ff6ac1") (set-face-foreground 'mini-echo-green "#5af78e") (set-face-foreground 'mini-echo-gray "#848688") (set-face-foreground 'mini-echo-yellow "#f3f99d") (set-face-foreground 'mini-echo-cyan "#9aedfe") (defun chris/mini-echo-minibuffer-width-lessp () "Return non-nil if current minibuffer window width less than 120." (< (mini-echo-minibuffer-width) 100)) (setq mini-echo-short-style-predicate 'chris/mini-echo-minibuffer-width-lessp) :config (defun chris/toggle-mode-lines () "Switch between doom and mini-echo" (interactive) (if mini-echo-mode (eval (mini-echo-mode 0) (doom-modeline-mode 1)) (eval (doom-modeline-mode 0) (mini-echo-mode 1))))) #+end_src *** Searching Let's make xref use ripgrep instead of grep for speed. #+begin_src emacs-lisp (setq xref-search-program 'ripgrep) #+end_src ** Fix NixOS :PROPERTIES: :ID: 20241114T164653.192420 :END: I am currently using NixOS. In order for emacs to have access to certain programs, we need to set some environment variables #+begin_src emacs-lisp (add-to-list 'exec-path "/usr/bin") (add-to-list 'exec-path "~/.cargo/bin") (setenv "NIX_CONF_DIR" "/etc/nix") (setenv "NIX_REMOTE" "daemon") (setenv "XCURSOR_THEME" "phinger-cursors-light") (setenv "QT_SCALE_FACTOR" "1") (setenv "QT_QPA_PLATFORM" "wayland;xcb") (setenv "CLUTTER_BACKEND" "wayland") (setenv "GDK_BACKEND" "wayland,x11") (setenv "SDL_VIDEODRIVER" "wayland") (setenv "XDG_SESSION_TYPE" "wayland") (defun chris/nix-reload () (interactive) (let* ((query (shell-command-to-string "nix-store --query $(which emacs)")) (store-path (expand-file-name "share/emacs" (string-trim query)))) (load-file (expand-file-name "site-lisp/subdirs.el" store-path)) (when (boundp 'native-comp-eln-load-path) (add-to-list 'native-comp-eln-load-path (expand-file-name "native-lisp/" store-path))))) #+end_src Also due to using greetd, emacs isn't started with the environment from the system, so let's make sure it knows which wayland display we are using #+begin_src emacs-lisp (setenv "WAYLAND_DISPLAY" "wayland-1") #+end_src #+RESULTS: : /nix/var/nix/profiles/per-user/%u #+begin_src emacs-lisp :tangle no (use-package exec-path-from-shell :demand :commands exec-path-from-shell-initialize :custom (exec-path-from-shell-arguments '("-l")) :config (exec-path-from-shell-initialize)) #+end_src ** Spell Check #+begin_src emacs-lisp (executable-find "ssh") (setq ispell-program-name "hunspell" ispell-local-dictionary "en_US" ispell-local-dictionary-alist ;; Please note the list `("-d" "en_US")` contains ACTUAL parameters passed to hunspell ;; You could use `("-d" "en_US,en_US-med")` to check with multiple dictionaries '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_US") nil utf-8))) (add-hook 'org-mode-hook 'chris/org-mode-setup) #+end_src ** Proced Let's turn auto update on for proced #+begin_src emacs-lisp (setq proced-auto-update-flag t) #+end_src ** Keep Folders Clean Let's use =no-littering= in order to stop emacs from filling all our folders with junk. #+begin_src emacs-lisp (use-package no-littering) ;; no-littering doesn't set this by default so we must place ;; auto save files in the same path as it uses for sessions (setq auto-save-file-name-transforms `((".*" ,(no-littering-expand-var-file-name "auto-save/") t))) (setq backup-directory-alist '(("." . "~/.emacs.d/backup")) backup-by-copying t ; Don't delink hardlinks version-control t ; Use version numbers on backups delete-old-versions t ; Automatically delete excess backups kept-new-versions 20 ; how many of the newest versions to keep kept-old-versions 5 ; and how many of the old ) #+end_src ** Ligatures Here let's try to add ligatures to our font system since the VictorMono Nerd Font supports all ligatures being a "Nerd Font". #+begin_src emacs-lisp (use-package ligature :load-path "path-to-ligature-repo" :config ;; Enable the "www" ligature in every possible major mode (ligature-set-ligatures 't '("www")) ;; Enable traditional ligature support in eww-mode, if the ;; `variable-pitch' face supports it (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi")) ;; Enable all Cascadia and Fira Code ligatures in programming modes (ligature-set-ligatures 'prog-mode '(;; == === ==== => =| =>>=>=|=>==>> ==< =/=//=// =~ ;; =:= =!= ("=" (rx (+ (or ">" "<" "|" "/" "~" ":" "!" "=")))) ;; ;; ;;; (";" (rx (+ ";"))) ;; && &&& ("&" (rx (+ "&"))) ;; !! !!! !. !: !!. != !== !~ ("!" (rx (+ (or "=" "!" "\." ":" "~")))) ;; ?? ??? ?: ?= ?. ("?" (rx (or ":" "=" "\." (+ "?")))) ;; %% %%% ("%" (rx (+ "%"))) ;; |> ||> |||> ||||> |] |} || ||| |-> ||-|| ;; |->>-||-<<-| |- |== ||=|| ;; |==>>==<<==<=>==//==/=!==:===> ("|" (rx (+ (or ">" "<" "|" "/" ":" "!" "}" "\]" "-" "=" )))) ;; \\ \\\ \/ ("\\" (rx (or "/" (+ "\\")))) ;; ++ +++ ++++ +> ("+" (rx (or ">" (+ "+")))) ;; :: ::: :::: :> :< := :// ::= (":" (rx (or ">" "<" "=" "//" ":=" (+ ":")))) ;; // /// //// /\ /* /> /===:===!=//===>>==>==/ ("/" (rx (+ (or ">" "<" "|" "/" "\\" "\*" ":" "!" "=")))) ;; .. ... .... .= .- .? ..= ..< ("\." (rx (or "=" "-" "\?" "\.=" "\.<" (+ "\.")))) ;; -- --- ---- -~ -> ->> -| -|->-->>->--<<-| ("-" (rx (+ (or ">" "<" "|" "~" "-")))) ;; *> */ *) ** *** **** ("*" (rx (or ">" "/" ")" (+ "*")))) ;; www wwww ("w" (rx (+ "w"))) ;; <>