eshell | ||
init.el | ||
README.org |
Chris's Personal Emacs Config
Table of Contents toc
Early Init
As of right now I haven't fully setup my early-init file, this does not export into the early-init.el file currently as it's just a copy from Doom's early-init.
;;; early-init.el -*- lexical-binding: t; -*-
;; Emacs 27.1 introduced early-init.el, which is run before init.el, before
;; package and UI initialization happens, and before site files are loaded.
;; A big contributor to startup times is garbage collection. We up the gc
;; threshold to temporarily prevent it from running, then reset it later by
;; enabling `gcmh-mode'. Not resetting it will cause stuttering/freezes.
(setq gc-cons-threshold most-positive-fixnum)
;; In noninteractive sessions, prioritize non-byte-compiled source files to
;; prevent the use of stale byte-code. Otherwise, it saves us a little IO time
;; to skip the mtime checks on every *.elc file.
(setq load-prefer-newer noninteractive)
;; In Emacs 27+, package initialization occurs before `user-init-file' is
;; loaded, but after `early-init-file'. Doom handles package initialization, so
;; we must prevent Emacs from doing it early!
(setq package-enable-at-startup nil)
(fset #'package--ensure-init-file #'ignore) ; DEPRECATED Removed in 28
;; `file-name-handler-alist' is consulted on every `require', `load' and various
;; path/io functions. You get a minor speed up by nooping this. However, this
;; may cause problems on builds of Emacs where its site lisp files aren't
;; byte-compiled and we're forced to load the *.el.gz files (e.g. on Alpine)
(unless (daemonp)
(defvar doom--initial-file-name-handler-alist file-name-handler-alist)
(setq file-name-handler-alist nil)
;; Restore `file-name-handler-alist' later, because it is needed for handling
;; encrypted or compressed files, among other things.
(defun doom-reset-file-handler-alist-h ()
;; Re-add rather than `setq', because changes to `file-name-handler-alist'
;; since startup ought to be preserved.
(dolist (handler file-name-handler-alist)
(add-to-list 'doom--initial-file-name-handler-alist handler))
(setq file-name-handler-alist doom--initial-file-name-handler-alist))
(add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h))
;; Ensure Doom is running out of this file's directory
(setq user-emacs-directory (file-name-directory load-file-name))
;; Load the heart of Doom Emacs
(load (concat user-emacs-directory "core/core") nil 'nomessage)
Init
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.
;;; init.el -*- lexical-binding: t; -*-
(setq inhibit-startup-message t)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(tooltip-mode -1)
(set-fringe-mode 1)
(menu-bar-mode -1)
(blink-cursor-mode -1)
(column-number-mode +1)
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.
(if (string-equal (system-name) "chris-linuxlaptop")
(defvar chris/default-font-size 240)
(defvar chris/default-font-size 120))
(set-face-attribute 'default nil :font "VictorMono Nerd Font" :height chris/default-font-size)
(set-face-attribute 'fixed-pitch nil :font "VictorMono Nerd Font" :height chris/default-font-size)
(set-face-attribute 'variable-pitch nil :font "Cantarell" :height chris/default-font-size :weight 'regular)
Then let's make sure line-numbers are relative and on. And let's turn on visual-line-mode globally.
(setq display-line-numbers-type 'relative)
(display-line-numbers-mode +1)
(global-visual-line-mode +1)
Also, real quick let's make sure that <escape>
works as the same as <C-g>
(global-set-key (kbd "<escape>") 'keyboard-escape-quit)
Startup Performance
(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)
Let's bootstrap straight.el
(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)
(use-package command-log-mode)
(use-package all-the-icons)
(use-package doom-modeline
:ensure t
:init
(doom-modeline-mode 1)
(setq doom-modeline-height 35
doom-modeline-bar-width 3
all-the-icons-scale-factor 0.9))
(use-package doom-themes
:ensure t
:init (load-theme 'doom-snazzy t))
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))
(use-package adaptive-wrap)
(use-package which-key
:init (which-key-mode)
:config
(setq which-key-idle-delay 0.3))
Keybindings
There are two major packages we need to get the functionality I desire. Evil and general.
(use-package evil
:init
(setq evil-want-integration t
evil-want-keybinding nil
evil-want-C-i-jump nil
evil-want-C-u-scroll t
evil-respect-visual-line-mode t
evil-want-C-u-delete t)
:config
(evil-mode +1))
This evil-collection package includes a lot of other evil based things.
(use-package evil-collection
:after evil
:config (evil-collection-init))
(use-package general
:init
(general-evil-setup)
:config
(general-create-definer chris/leader-keys
:keymaps '(normal visual emacs)
:prefix "SPC")
(chris/leader-keys
"b" '(:ignore t :which-key "buffer")
"t" '(:ignore t :which-key "toggle")
"f" '(:ignore t :which-key "file")
"w" '(:ignore t :which-key "window")
"s" '(:ignore t :which-key "search")
"o" '(:ignore t :which-key "open")
"h" '(:ignore t :which-key "help")
"n" '(:ignore t :which-key "notes")
"bs" '(consult-buffer :which-key "buffer search")
"bd" '(kill-this-buffer :which-key "kill buffer")
"nf" '(org-roam-find-file :which-key "org roam ff")
"tt" '(consult-theme :which-key "choose theme")
"ff" '(find-file :which-key "find file")
"fr" '(consult-recent-file :which-key "recent file")
"fs" '(save-buffer :which-key "save")
"hf" '(helpful-callable :which-key "describe-function")
"hv" '(helpful-variable :which-key "describe-variable")
"od" '(dired-jump :which-key "dired jump")
"ss" '(consult-line :which-key "consult search")
"ww" '(other-window :which-key "other window")
"wd" '(delete-window :which-key "other window")
))
(use-package evil-escape
:after evil
:init (evil-escape-mode +1)
:config (setq evil-escape-key-sequence "fd"))
Better UI
Olivetti
(use-package olivetti)
TOC-ORG
(use-package toc-org
:after org)
Completion
SELECTRUM
I prefer selectrum over Ivy or Helm for completions. It is using the basic completing read system and therefore it is more inline with basic emacs. Also, let's add prescient to be able to filter selectrum well. We'll add some keybindings too for easier navigation on the home row.
(use-package selectrum
:init
(selectrum-mode +1)
:config
(general-define-key
:keymaps 'selectrum-minibuffer-map
"C-j" 'selectrum-next-candidate
"C-k" 'selectrum-previous-candidate
"C-S-j" 'selectrum-goto-end
"C-S-k" 'selectrum-goto-beginning
"TAB" 'selectrum-insert-current-candidate)
:commands (completing-read))
We need prescient so we can have smarter sorting and filtering by default. Ontop of that, setting persistance in prescient makes it get better over time.
(use-package prescient
:config
(prescient-persist-mode +1)
:after selectrum)
(use-package selectrum-prescient
:init
(selectrum-prescient-mode +1)
:after selectrum)
;; enable company use of prescient
(company-prescient-mode +1)
;; enable magit to read with prescient
(setq magit-completing-read-function #'selectrum-completing-read)
Here we use posframe to make a prettier minibuffer. Posframe will work with EXWM with some tweaking, but I only stole some code for Ivy's posframe version, not selectrum's. So, I'll need to work on that.
(setq selectrum-display-action '(display-buffer-show-in-posframe))
(setq selectrum-display-action nil)
(defun display-buffer-show-in-posframe (buffer _alist)
(frame-root-window
(posframe-show buffer
:min-height 10
:min-width (/ (frame-width) 2)
:internal-border-width 1
:left-fringe 18
:right-fringe 18
:parent-frame nil
:z-group 'above
:poshandler 'posframe-poshandler-frame-center)))
(add-hook 'minibuffer-exit-hook 'posframe-delete-all)
This is similar but using mini-frame. Mini-frame works well, but not if using exwm. With exwm the X windows get displayed above the mini-frame, so the minibuffer isn't visible. Best to let Selectrum or Consult push the frame up and view the vertical completions below the frame.
(mini-frame-mode +1)
(mini-frame-mode -1)
(setq resize-mini-frames t)
(custom-set-variables
'(mini-frame-show-parameters
'((top . 400)
(width . 0.7)
(left . 0.5))))
;; workaround bug#44080, should be fixed in version 27.2 and above, see #169
(define-advice fit-frame-to-buffer (:around (f &rest args) dont-skip-ws-for-mini-frame)
(cl-letf* ((orig (symbol-function #'window-text-pixel-size))
((symbol-function #'window-text-pixel-size)
(lambda (win from to &rest args)
(apply orig
(append (list win from
(if (and (window-minibuffer-p win)
(frame-root-window-p win)
(eq t to))
nil
to))
args)))))
(apply f args)))
fit-frame-to-buffer@dont-skip-ws-for-mini-frame
CONSULT
Consult has a lot of nice functions like Ivy's Counsel functions (enhanced searching functions), lets set some of them in the keymap so they are easily used.
(use-package consult)
(map! :leader "s s" 'consult-line
:leader "f r" 'consult-recent-file)
MARGINALIA
Marginalia makes for some great decoration to our minibuffer completion items. Works great with Selectrum which does not have this out of the box.
;; Enable richer annotations using the Marginalia package
(use-package marginalia
:bind (:map minibuffer-local-map
("C-M-a" . marginalia-cycle)
;; :map embark-general-map
;; ("A" . marginalia-cycle)
)
;; The :init configuration is always executed (Not lazy!)
:init
;; Must be in the :init section of use-package such that the mode gets
;; enabled right away. Note that this forces loading the package.
(marginalia-mode)
;; When using Selectrum, ensure that Selectrum is refreshed when cycling annotations.
(advice-add #'marginalia-cycle :after
(lambda () (when (bound-and-true-p selectrum-mode) (selectrum-exhibit))))
;; Prefer richer, more heavy, annotations over the lighter default variant.
(setq marginalia-annotators '(marginalia-annotators-heavy marginalia-annotators-light nil))
:after selectrum)
Help
(use-package helpful)
Format
(use-package format-all
:config
(format-all-mode +1))
Org Mode
Need to setup auto tangle and make sure I have some structure templates for org-mode.
(use-package org
:config
(setq org-startup-indented t)
(defun chris/org-babel-tangle-config ()
(when (string-equal (buffer-file-name)
(expand-file-name "~/.personal-emacs/init.org"))
(let ((org-confirm-babel-evaluate nil))
(org-babel-tangle))))
(add-hook 'org-mode-hook (lambda () (add-hook 'after-save-hook #'chris/org-babel-tangle-config)))
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(python . t)
(shell . t)))
(require 'org-tempo)
(add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("py" . "src python"))
(add-to-list 'org-structure-template-alist '("sh" . "src shell"))
(add-to-list 'org-structure-template-alist '("q" . "quote"))
(setq org-capture-templates
'(("t" "Personal todo" entry
(file+headline +org-capture-todo-file "Inbox")
"* TODO %^{TODO name}\nSCHEDULED: %T\n%a\n%i%?" :prepend t)
("n" "Personal notes" entry
(file+headline +org-capture-notes-file "Inbox")
"* %u %?\n%i\n%a" :prepend t)
("j" "Journal" entry
(file+olp+datetree +org-capture-journal-file)
"* %U %?\n%i\n%a" :prepend t)
("p" "TFC Plan" entry
(function chris/org-roam-capture-lesson-file)
(file ".templates/tfcplantemplate.org")
:prepend nil
:jump-to-captured t
:empty-lines 1)
("r" "Templates for projects")
("rt" "Project-local todo" entry
(file+headline +org-capture-project-todo-file "Inbox")
"* TODO %?\n%i\n%a" :prepend t)
("rn" "Project-local notes" entry
(file+headline +org-capture-project-notes-file "Inbox")
"* %U %?\n%i\n%a" :prepend t)
("rc" "Project-local changelog" entry
(file+headline +org-capture-project-changelog-file "Unreleased")
"* %U %?\n%i\n%a" :prepend t)
("o" "Centralized templates for projects")
("ot" "Project todo" entry #'+org-capture-central-project-todo-file
"* TODO %?\n %i\n %a" :heading "Tasks" :prepend nil)
("on" "Project notes" entry #'+org-capture-central-project-notes-file
"* %U %?\n %i\n %a" :heading "Notes" :prepend t)
("oc" "Project changelog" entry #'+org-capture-central-project-changelog-file
"* %U %?\n %i\n %a" :heading "Changelog" :prepend t))
org-capture-use-agenda-date t)
;;(setq org-superstar-headline-bullets-list '("◉" "◈" "▸" "✬" "◎" "◇" "❉" "✙" "❖"))
(setq olivetti-body-width 0.6)
(setq olivetti-minimum-body-width 100)
(setq org-imenu-depth 4)
(setq org-odt-styles-file "/home/chris/org/style.odt")
(setq org-todo-keywords
'((sequence "TODO(t)" "PROJ(p)" "STRT(s)" "WAIT(w)" "HOLD(h)" "|" "DONE(d)" "CNCL(c)")
(sequence "[ ](T)" "[-](S)" "[?](W)" "|" "[X](D)")))
(add-hook 'org-mode-hook
(toc-org-mode +1)
(olivetti-mode +1))
(setq org-agenda-files
'("/home/chris/org/DMPREADME.org" "/home/chris/org/DMPTODO.org" "/home/chris/org/inbox.org" "/home/chris/org/notes.org" "/home/chris/org/repetition.org" "/home/chris/org/tasks.org" "/home/chris/org/tfc_plans.org" "/home/chris/org/ministry_team.org" "/home/chris/org/todo.org" "/home/chris/org/newsletter.org"))
(setq org-id-method 'ts)
:general
(chris/leader-keys "o a" 'org-agenda)
(chris/leader-keys "c" 'org-capture))
We need to create a lesson capture function to find our lesson files differently each time we run our TFC plan capture.
(defun chris/org-roam-capture-lesson-file ()
"Function to return the lesson file that is needed for TFC plan capture and move to correct position for plan insertion"
(interactive)
(unless org-roam-mode (org-roam-mode))
(let* ((completions (org-roam--get-title-path-completions))
(title-with-tags (org-roam-completion--completing-read "Lesson: " completions))
(res (cdr (assoc title-with-tags completions)))
(file-path (plist-get res :path)))
(find-file file-path)
(goto-char (point-min))
(search-forward "PLAN")))
Org-Roam
Here we are going to add org-roam. This is a note-takers paradise by adding an automatic backlinking function.
Basic Org-Roam setup. We select the directory and the basic width of the Org-Roam buffer so that it fits right. We also want to exclude certain files from Org-Roam. All files are synced between machines using syncthing and kept in a version history. I'd like to exclude the version history from Org-Roam using org-roam-file-exclude-regexp
.
We also need to setup some capture templates to use some specific setups with my journalling. These include a space for my Homework For Life, tasks for the day, and how I can love on my family.
(use-package org-roam
:after org
:config
(setq org-roam-directory "~/org")
(setq org-roam-buffer-width 0.25)
(setq org-roam-file-exclude-regexp ".stversion.*\|.stfolder.*\|.*~.*\|.*sync.*")
(setq org-roam-db-location "~/.dotemacs/org-roam.db")
(setq org-roam-capture-templates
'(("d" "default" plain (function org-roam--capture-get-point)
"%?"
:file-name "${slug}"
:head "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\nj ")
("b" "bible" plain (function org-roam--capture-get-point)
"%?"
:file-name "${slug}"
:head "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n- tags %^G\n\n* ")
("l" "TFC Lesson" plain (function org-roam--capture-get-point)
(file ".templates/lessontemplate.org")
:file-name "${slug}"
:head "#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n")))
(setq org-roam-dailies-capture-templates
'(("d" "daily" plain #'org-roam-capture--get-point ""
:immediate-finish t
:file-name "%<%Y-%m-%d>"
:head "#+TITLE: %<%Y-%m-%d>\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\n* HFL\n* Tasks\n* Family\n** How Do I Love Abbie?")
("b" "biblical daily" plain #'org-roam-capture--get-point ""
:immediate-finish t
:file-name "%<%Y-%m-%d>-bib"
:head "#+TITLE: %<%Y-%m-%d> - Biblical\n#+AUTHOR: Chris Cochrun"))))
Org-Roam server. This let's me visualize my notes. In order to use it, I need to go to http://localhost:8080
(use-package org-roam-server
:config
(setq org-roam-server-host "127.0.0.1"
org-roam-server-port 8080
org-roam-server-export-inline-images t
org-roam-server-authenticate nil
org-roam-server-serve-files t
org-roam-server-network-label-truncate t
org-roam-server-network-label-truncate-length 60
org-roam-server-network-label-wrap-length 20)
:after org-roam)
(add-hook 'org-roam-mode-hook org-roam-server-mode t)
Magit
Use magit, because why wouldn't you? duh!
(use-package magit
:commands (magit-status magit-get-current-branch)
:custom
(magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1))