#+TITLE: Chris's Personal Emacs Config #+AUTHOR: Chris Cochrun * 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]] - [[#keep-folders-clean][Keep Folders Clean]] - [[#ligatures][Ligatures]] - [[#keybindings][Keybindings]] - [[#undo-tree][Undo-Tree]] - [[#better-ui][Better UI]] - [[#completion][Completion]] - [[#navigation][Navigation]] - [[#help][Help]] - [[#format][Format]] - [[#languages][Languages]] - [[#file-management][File Management]] - [[#org-mode][Org Mode]] - [[#mu4e][MU4E]] - [[#calendar][Calendar]] - [[#magit][Magit]] - [[#eshell][Eshell]] - [[#sly][Sly]] - [[#pdf-tools][PDF-Tools]] - [[#epub][EPUB]] - [[#window-management][Window Management]] - [[#elfeed][Elfeed]] - [[#bongo][Bongo]] - [[#transmission][Transmission]] - [[#garbage-collection][Garbage Collection]] - [[#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. #+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) (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) #+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 240) (defvar chris/default-font-size 120)) (defun chris/set-font-faces () "Set the faces for our fonts" (message "Setting faces!") (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 (/ chris/default-font-size 8)) :weight 'regular)) (if (daemonp) (add-hook 'after-make-frame-functions (lambda (frame) (with-selected-frame frame (chris/set-font-faces)))) (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 'relative) (global-display-line-numbers-mode +1) (add-hook 'prog-mode-hook (display-line-numbers-mode +1)) (global-visual-line-mode +1) #+end_src Let's make doc-view better #+begin_src emacs-lisp (setq doc-view-resolution 192) #+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) #+end_src ** Let's bootstrap straight.el #+begin_src emacs-lisp (setq straight-fix-org t) (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) (setq use-package-verbose t) #+end_src #+begin_src emacs-lisp (use-package command-log-mode :commands command-log-mode) #+end_src #+begin_src emacs-lisp (use-package all-the-icons) #+end_src #+begin_src emacs-lisp (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) (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 (use-package doom-themes :ensure t :init (load-theme 'doom-snazzy t)) #+end_src #+begin_src emacs-lisp (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) #+end_src #+begin_src emacs-lisp (use-package smartparens :defer 1 :config (smartparens-global-mode +1)) #+end_src #+begin_src emacs-lisp (use-package aggressive-indent :defer 1 :config (global-aggressive-indent-mode +1)) #+end_src #+begin_src emacs-lisp (use-package adaptive-wrap :defer t) #+end_src #+begin_src emacs-lisp (use-package which-key :config (setq which-key-idle-delay 0.3) (which-key-mode) :defer 1) #+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))) #+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 (let ((alist '((?! . "\\(?:!\\(?:==\\|[!=]\\)\\)") (?# . "\\(?:#\\(?:###?\\|_(\\|[!#(:=?[_{]\\)\\)") (?$ . "\\(?:\\$>\\)") (?& . "\\(?:&&&?\\)") (?* . "\\(?:\\*\\(?:\\*\\*\\|[/>]\\)\\)") (?+ . "\\(?:\\+\\(?:\\+\\+\\|[+>]\\)\\)") (?- . "\\(?:-\\(?:-[>-]\\|<<\\|>>\\|[<>|~-]\\)\\)") (?. . "\\(?:\\.\\(?:\\.[.<]\\|[.=?-]\\)\\)") (?/ . "\\(?:/\\(?:\\*\\*\\|//\\|==\\|[*/=>]\\)\\)") (?: . "\\(?::\\(?:::\\|\\?>\\|[:<-?]\\)\\)") (?\; . "\\(?:;;\\)") (?< . "\\(?:<\\(?:!--\\|\\$>\\|\\*>\\|\\+>\\|-[<>|]\\|/>\\|<[<=-]\\|=\\(?:=>\\|[<=>|]\\)\\||\\(?:||::=\\|[>|]\\)\\|~[>~]\\|[$*+/:<=>|~-]\\)\\)") (?= . "\\(?:=\\(?:!=\\|/=\\|:=\\|=[=>]\\|>>\\|[=>]\\)\\)") (?> . "\\(?:>\\(?:=>\\|>[=>-]\\|[]:=-]\\)\\)") (?? . "\\(?:\\?[.:=?]\\)") (?\[ . "\\(?:\\[\\(?:||]\\|[<|]\\)\\)") (?\ . "\\(?:\\\\/?\\)") (?\] . "\\(?:]#\\)") (?^ . "\\(?:\\^=\\)") (?_ . "\\(?:_\\(?:|?_\\)\\)") (?{ . "\\(?:{|\\)") (?| . "\\(?:|\\(?:->\\|=>\\||\\(?:|>\\|[=>-]\\)\\|[]=>|}-]\\)\\)") (?~ . "\\(?:~\\(?:~>\\|[=>@~-]\\)\\)")))) (dolist (char-regexp alist) (set-char-table-range composition-function-table (car char-regexp) `([,(cdr char-regexp) 0 font-shape-gstring])))) #+end_src ** Keybindings There are two major packages we need to get the functionality I desire. Evil and general. #+begin_src emacs-lisp (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) (setq evil-undo-system 'undo-tree)) #+end_src This evil-collection package includes a lot of other evil based things. #+begin_src emacs-lisp (use-package evil-collection :after evil :config (evil-collection-init)) #+end_src #+begin_src emacs-lisp (use-package general :init (general-evil-setup) :config (general-create-definer chris/leader-keys :keymaps '(normal visual emacs) :prefix "SPC") (chris/leader-keys :states 'normal :keymaps 'override "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") "bi" '(ibuffer :which-key "ibuffer") "tt" '(consult-theme :which-key "choose theme") "ff" '(find-file :which-key "find file") "fb" '((find-file ~/org/bibles/) :which-key "find bible book") "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") "hk" '(helpful-key :which-key "describe-key") "hb" '(general-describe-keybindings :which-key "describe-bindings") "hi" '(info :which-key "info manual") "ss" '(consult-line :which-key "consult search") "ww" '(other-window :which-key "other window") "wd" '(delete-window :which-key "other window") "wv" '(evil-window-vsplit :which-key "split window vertically") "ws" '(evil-window-split :which-key "split window horizontally") ";" '(execute-extended-command :which-key "execute command") ":" '(eval-expression :which-key "evaluate expression") ) (general-def 'minibuffer-local-map "C-v" 'evil-paste-after) (general-def 'normal "gcc" 'comment-line)) #+end_src #+begin_src emacs-lisp (use-package evil-escape :after evil :init (evil-escape-mode +1) :config (setq evil-escape-key-sequence "fd" evil-escape-delay 0.3)) #+end_src #+begin_src emacs-lisp (use-package evil-surround :after evil :config (global-evil-surround-mode +1)) #+end_src ** Undo-Tree #+begin_src emacs-lisp (use-package undo-tree :defer 1 :config (global-undo-tree-mode +1) :general (general-def 'normal undo-tree-visualize-mode-map "j" 'undo-tree-visualize-redo "k" 'undo-tree-visualize-undo)) #+end_src ** Better UI *** Olivetti #+begin_src emacs-lisp (use-package olivetti :after org :config (setq olivetti-body-width 0.6 olivetti-minimum-body-width 100)) #+end_src *** Visual-Fill-Line-Mode Visual fill column does a lot of the same as olivetti, but works with visual line mode better. #+begin_src emacs-lisp (use-package visual-fill-column :after org :config (setq visual-fill-column-width 100 visual-fill-column-center-text t)) #+end_src *** TOC-ORG #+begin_src emacs-lisp (use-package toc-org :after org) #+end_src ** Completion My completion framework is a combination of packages so that everything remains seperate and lightweight. *** 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. #+BEGIN_SRC emacs-lisp (use-package selectrum :init (selectrum-mode +1) :config (setq selectrum-max-window-height 8) (add-hook 'selectrum-mode-hook 'selectrum-exhibit) ;; We need to fix selectrums minibuffer handling for Emacs 28 (defun selectrum--set-window-height (window &optional height) "Set window height of WINDOW to HEIGHT pixel. If HEIGHT is not given WINDOW will be updated to fit its content vertically." (let* ((lines (length (split-string (overlay-get selectrum--candidates-overlay 'after-string) "\n" t))) (dheight (or height (* lines selectrum--line-height))) (wheight (window-pixel-height window)) (window-resize-pixelwise t)) (window-resize window (- dheight wheight) nil nil 'pixelwise))) :general ('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) #+END_SRC 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. #+begin_src emacs-lisp (use-package prescient :config (prescient-persist-mode +1) :after selectrum) #+end_src #+BEGIN_SRC emacs-lisp (use-package selectrum-prescient :init (selectrum-prescient-mode +1) :after selectrum) #+END_SRC #+BEGIN_SRC elisp :tangle no ;; enable company use of prescient (company-prescient-mode +1) ;; enable magit to read with prescient (setq magit-completing-read-function #'selectrum-completing-read) #+END_SRC 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. #+BEGIN_SRC elisp :tangle no (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) #+END_SRC #+RESULTS: 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. #+BEGIN_SRC elisp :tangle no (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))) #+END_SRC *** 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. #+begin_src emacs-lisp (use-package consult :after selectrum :config (setq consult-narrow-key "<") :general (chris/leader-keys :states 'normal :keymaps 'override "si" 'consult-imenu "so" 'consult-outline)) #+end_src #+begin_src emacs-lisp :tangle no (map! :leader "s s" 'consult-line :leader "f r" 'consult-recent-file) #+end_src *** 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. #+begin_src emacs-lisp (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) #+end_src *** Company #+begin_src emacs-lisp (use-package company :defer 1 :config (global-company-mode +1)) #+end_src #+begin_src emacs-lisp (use-package company-dict :defer t) #+end_src ** Navigation *** Avy Avy provides a lot of functions to search through the current buffer. Most of the time I use evil or consult functions to find what I'm looking for, but avy provides a lot of small movements that are more useful for visible movements. #+begin_src emacs-lisp (use-package avy :after evil) #+end_src These are some evil bindings to avy. #+begin_src emacs-lisp (use-package evil-avy :after avy :general (general-define-key :states 'normal :keymaps '(override magit-mode-map) "F" 'magit-pull)) #+end_src *** Ace-Link Ace link provides an avy like search for links. Upon using the keybindings presented it opens the url. #+begin_src emacs-lisp (use-package ace-link :after avy) #+end_src ** Help #+begin_src emacs-lisp (use-package helpful :commands (helpful-callable helpful-variable helpful-command helpful-key)) #+end_src ** Format #+begin_src emacs-lisp (use-package format-all :config (format-all-mode +1) (setq format-all-formatters '("Emacs Lisp" emacs-lisp)) :defer 1) #+end_src ** Languages *** Fennel I use fennel to build my awesomewm config. So, we'll need that downloaded. #+begin_src emacs-lisp (use-package fennel-mode :mode ("\\.fnl\\'" . fennel-mode)) #+end_src *** Friar Friar is a fennel repl in the awesome repl. It allows you to interact with AwesomeWM from inside emacs. #+begin_src emacs-lisp (use-package friar :straight (:host github :repo "warreq/friar" :branch "master" :files (:defaults "*.lua" "*.fnl"))) #+end_src *** Yaml I do a lot of docker management so having yaml is necessary #+begin_src emacs-lisp (use-package yaml-mode :mode ("\\.yml\\'" . yaml-mode)) #+end_src Docker itself #+begin_src emacs-lisp (use-package docker :defer t) #+end_src Let's make sure docker is capable of using tramp. #+begin_src emacs-lisp (use-package docker-tramp :after docker) #+end_src Still need dockerfile-mode in order to be able to edit dockerfiles. *** Fish Fish is my preferred shell and made some scripts in it so I keep this to be able to edit those scripts. #+begin_src emacs-lisp (use-package fish-mode :mode ("\\.fish\\'" . fish-mode)) #+end_src *** QML I make some apps in Qt 5 so QML is a needed language although the support in Emacs is lacking. #+begin_src emacs-lisp (use-package qml-mode :mode ("\\.qml\\'" . qml-mode) :config (use-package company-qml :after qml-mode :config (add-to-list 'company-backends 'company-qml) (setq company-qml-extra-qmltypes-files '("/home/chris/.Felgo/Felgo/gcc_64/import/VPlayPlugins/vplayplugins.qmltypes" "/home/chris/.Felgo/Felgo/gcc_64/import/VPlayApps/vplayapps.qmltypes" "/home/chris/.Felgo/Felgo/gcc_64/import/VPlay/vplay.qmltypes" "/home/chris/.Felgo/Felgo/gcc_64/import/Felgo/felgo.qmltypes" "/home/chris/.Felgo/Felgo/gcc_64/qml")) (setq company-idle-delay 0.1))) (use-package qt-pro-mode :after qml-mode) #+end_src ** File Management *** Dired I'm making a small function in here to open files in the appropriate program using XDG defaults. This is like opening odt files in Libreoffice or mp4 files in MPV. #+begin_src emacs-lisp (use-package dired :ensure nil :straight nil :config (defun chris/dired-open-xdg () "Open the file-at-point in the appropriate program" (interactive) (let ((file (ignore-errors (dired-get-file-for-visit)))) (browse-url-xdg-open (file-truename file)))) :general (chris/leader-keys :states 'normal :keymaps 'override "od" '(dired-jump :which-key "open dired here")) (general-def 'normal dired-mode-map "q" 'kill-this-buffer "C-" 'chris/dired-open-xdg)) #+end_src We need a function to copy the full filename to kill-ring #+begin_src emacs-lisp (defun chris/dired-yank-filename () "Get the full filename from file at point and put into kill-ring" (interactive) (let* ((file (dired-get-filename))) (clipboard-kill-ring-save nil nil file))) #+end_src #+begin_src emacs-lisp (use-package all-the-icons-dired :hook (dired-mode . all-the-icons-dired-mode)) #+end_src #+begin_src emacs-lisp (use-package dired-single :after dired :general (general-def 'normal dired-mode-map "h" 'dired-single-up-directory "l" 'dired-single-buffer)) #+end_src #+begin_src emacs-lisp :tangle no (use-package dired-rainbow :after dired) #+end_src #+begin_src emacs-lisp (use-package diredfl :after dired :config (diredfl-global-mode +1)) #+end_src *** Tramp #+begin_src emacs-lisp (require 'tramp) (add-to-list 'tramp-default-proxies-alist '(nil "\\`root\\'" "/ssh:%h:")) (add-to-list 'tramp-default-proxies-alist '((regexp-quote (system-name)) nil nil)) #+end_src ** Org Mode Let's start by creating a self contained function of what I'd like started on every org buffer. #+begin_src emacs-lisp (defun chris/org-mode-setup () (interactive) (org-indent-mode +1) (toc-org-mode +1) (visual-fill-column-mode +1) (display-line-numbers-mode -1) (variable-pitch-mode +1)) #+end_src This is the use-package definition with a lot of customization. Need to setup auto tangle and make sure I have some structure templates for org-mode. Part of this config includes some special capture templates for my work as a youth minister. I create lessons through both org-mode and org-roam capture templates. The first part comes from org-roam, then the next is org-mode. #+begin_src emacs-lisp (use-package org :config (setq org-startup-indented t org-edit-src-content-indentation 0 org-agenda-sticky t org-fontify-quote-and-verse-blocks t) (add-hook 'org-mode-hook 'chris/org-mode-setup) (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 '("yaml" . "src yaml")) (add-to-list 'org-structure-template-alist '("yml" . "src yaml")) (add-to-list 'org-structure-template-alist '("q" . "quote")) (setq org-capture-templates '(("t" "Personal todo" entry (file+headline "todo.org" "Inbox") "* TODO %^{TODO name}\nSCHEDULED: %T\n%a\n%i%?" :prepend t) ("n" "Personal notes" entry (file+headline "notes.org" "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) ("P" "TFC Posts" entry (file+headline "/home/chris/org/nvtfc_social_media.org" "Posts") (file ".templates/posts.org") :prepend t :jump-to-captured t) ("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 org-imenu-depth 4) (setq org-odt-styles-file "/home/chris/org/style.odt") (setq org-export-with-toc nil) (setq org-export-with-author nil) (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)"))) (setq org-agenda-files '("/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" )) (add-to-list '("/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" "/home/chris/org/lesson_*.org") (expand-file-name )) (setq org-id-method 'ts) (defun chris/org-columns-view () "Turn on org-columns overlay and turn off olivetti-mode" (interactive) (goto-char (point-min)) (org-content) (org-columns) (olivetti-mode -1)) (defun chris/org-columns-quit () "Remove the org-columns overlay and turn on olivetti-mode" (interactive) (org-columns-quit) (chris/org-mode-setup)) (add-hook 'org-agenda-finalize-hook 'evil-normal-state) ;;Let's make sure org-mode faces are inheriting fixed pitch faces. (dolist (face '(org-block org-block-begin-line org-block-end-line org-code org-document-info-keyword org-meta-line org-table org-verbatim)) (set-face-attribute `,face nil :inherit 'fixed-pitch)) (set-face-attribute 'org-block-end-line nil :inherit 'org-block-begin-line) (set-face-attribute 'org-quote nil :background "#242631" :inherit 'fixed-pitch) :general (chris/leader-keys :states 'normal :keymaps 'override "o a" 'org-agenda "c" 'org-capture) ('normal org-agenda-mode-map "q" 'org-agenda-quit "r" 'org-agenda-redo "d" 'org-agenda-deadline "s" 'org-agenda-schedule) ('normal org-columns-map "j" 'outline-next-heading "h" 'outline-previous-heading "q" 'chris/org-columns-quit) ('normal org-mode-map "RET" 'chris/org-dwim-at-point "gC" 'chris/org-columns-view)) #+end_src We need to create a lesson capture function to find our lesson files differently each time we run our TFC plan capture. This is the most unique part of my capture template. This function uses =org-roam-find-file= to pick the lesson file that I need to add my lesson plan to. This way the lesson itself is created before the plan. #+begin_src emacs-lisp (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"))) #+end_src We are also going to make our config auto-tangle. This is so helpful on saving the .org file tangles out the config automatically. #+begin_src emacs-lisp (defun chris/org-babel-tangle-config () (when (string-equal (buffer-file-name) (expand-file-name "~/.emacs.d/README.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 :append :local))) #+end_src We also need to add =evil-org= to make better keybindings. #+begin_src emacs-lisp (use-package evil-org :after org) #+end_src *** Org-Super-Agenda Super Agenda gives me a really nice way of making the agenda view look a lot better with some better information included. #+begin_src emacs-lisp (use-package org-super-agenda :after org-agenda :init (setq org-super-agenda-groups '((:name "Today" :time-grid t :scheduled today) (:name "Due Today" :deadline today) (:name "Important" :priority "A") (:name "Overdue" :time-grid t :scheduled today) (:name "Due soon" :deadline future))) :config (org-super-agenda-mode) (setq org-super-agenda-header-map nil)) #+end_src *** 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 doesn't take too much space on my laptop. 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 [[file:../../org/homework_for_life.org][Homework For Life]], tasks for the day, and how I can love on my family. #+BEGIN_SRC emacs-lisp (use-package org-roam :after org :hook org-load :commands (org-roam org-roam-find-file) :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"))) :general (chris/leader-keys :states 'normal :keymaps 'override "nf" '(org-roam-find-file :which-key "org roam ff") "nr" 'org-roam)) #+END_SRC Org-Roam server. This let's me visualize my notes. In order to use it, I need to go to http://localhost:8080 #+BEGIN_SRC emacs-lisp (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) #+END_SRC *** Org-Superstar Org-Superstar makes the stars at the beginning of the line in =org-mode= a lot prettier. #+begin_src emacs-lisp (use-package org-superstar :after org :config (org-superstar-mode +1) (setq org-superstar-headline-bullets-list '("\u25c9" "\u25c8" "盛" "\u25ce" "\u272c" "\u25c7" "\u2749" "\u2719" "\u2756")) (setq org-superstar-item-bullet-alist '((?- . ?\u25b8) (?+ . ?\u2749) (?* . ?\u25c9))) (set-face-attribute 'org-superstar-item nil :inherit 'org-level-3) (add-hook 'org-mode-hook 'org-superstar-mode)) #+end_src *** Org DWIM I've stolen most of this code from Doom Emacs but we want to create a dwim-at-point function for Org-Mode that will fit under the =RET= key. This allows us to do a bunch of different functions depending on the context of point. #+begin_src emacs-lisp (defun chris/org-dwim-at-point (&optional arg) "Do-what-I-mean at point. If on a: - checkbox list item or todo heading: toggle it. - clock: update its time. - headline: cycle ARCHIVE subtrees, toggle latex fragments and inline images in subtree; update statistics cookies/checkboxes and ToCs. - footnote reference: jump to the footnote's definition - footnote definition: jump to the first reference of this footnote - table-row or a TBLFM: recalculate the table's formulas - table-cell: clear it and go into insert mode. If this is a formula cell, recaluclate it instead. - babel-call: execute the source block - statistics-cookie: update it. - latex fragment: toggle it. - link: follow it - otherwise, refresh all inline images in current tree." (interactive "P") (let* ((context (org-element-context)) (type (org-element-type context))) ;; skip over unimportant contexts (while (and context (memq type '(verbatim code bold italic underline strike-through subscript superscript))) (setq context (org-element-property :parent context) type (org-element-type context))) (pcase type (`headline (cond ((memq (bound-and-true-p org-goto-map) (current-active-maps)) (org-goto-ret)) ((and (fboundp 'toc-org-insert-toc) (member "TOC" (org-get-tags))) (toc-org-insert-toc) (message "Updating table of contents")) ((string= "ARCHIVE" (car-safe (org-get-tags))) (org-force-cycle-archived)) ((or (org-element-property :todo-type context) (org-element-property :scheduled context)) (org-todo (if (eq (org-element-property :todo-type context) 'done) (or (car (chris/org-get-todo-keywords-for (org-element-property :todo-keyword context))) 'todo) 'done)))) ;; Update any metadata or inline previews in this subtree (org-update-checkbox-count) (org-update-parent-todo-statistics) (when (and (fboundp 'toc-org-insert-toc) (member "TOC" (org-get-tags))) (toc-org-insert-toc) (message "Updating table of contents")) (let* ((beg (if (org-before-first-heading-p) (line-beginning-position) (save-excursion (org-back-to-heading) (point)))) (end (if (org-before-first-heading-p) (line-end-position) (save-excursion (org-end-of-subtree) (point)))) (overlays (ignore-errors (overlays-in beg end))) (latex-overlays (cl-find-if (lambda (o) (eq (overlay-get o 'org-overlay-type) 'org-latex-overlay)) overlays)) (image-overlays (cl-find-if (lambda (o) (overlay-get o 'org-image-overlay)) overlays))) (chris/org--toggle-inline-images-in-subtree beg end) (if (or image-overlays latex-overlays) (org-clear-latex-preview beg end) (org--latex-preview-region beg end)))) (`clock (org-clock-update-time-maybe)) (`footnote-reference (org-footnote-goto-definition (org-element-property :label context))) (`footnote-definition (org-footnote-goto-previous-reference (org-element-property :label context))) ((or `planning `timestamp) (org-follow-timestamp-link)) ((or `table `table-row) (if (org-at-TBLFM-p) (org-table-calc-current-TBLFM) (ignore-errors (save-excursion (goto-char (org-element-property :contents-begin context)) (org-call-with-arg 'org-table-recalculate (or arg t)))))) (`table-cell (org-table-blank-field) (org-table-recalculate arg) (when (and (string-empty-p (string-trim (org-table-get-field))) (bound-and-true-p evil-local-mode)) (evil-change-state 'insert))) (`babel-call (org-babel-lob-execute-maybe)) (`statistics-cookie (save-excursion (org-update-statistics-cookies arg))) ((or `src-block `inline-src-block) (org-babel-execute-src-block)) ((or `latex-fragment `latex-environment) (org-latex-preview)) (`link (let* ((lineage (org-element-lineage context '(link) t)) (path (org-element-property :path lineage))) (if (or (equal (org-element-property :type lineage) "img") (and path (image-type-from-file-name path))) (chris/org--toggle-inline-images-in-subtree (org-element-property :begin lineage) (org-element-property :end lineage)) (org-open-at-point arg)))) (`paragraph (let ((match (and (org-at-item-checkbox-p) (match-string 1)))) (org-toggle-checkbox))) (_ (if (or (org-in-regexp org-ts-regexp-both nil t) (org-in-regexp org-tsr-regexp-both nil t) (org-in-regexp org-link-any-re nil t)) (call-interactively #'org-open-at-point) (chris/org--toggle-inline-images-in-subtree (org-element-property :begin context) (org-element-property :end context))))))) (defun chris/org-get-todo-keywords-for (&optional keyword) "Returns the list of todo keywords that KEYWORD belongs to." (when keyword (cl-loop for (type . keyword-spec) in (cl-remove-if-not #'listp org-todo-keywords) for keywords = (mapcar (lambda (x) (if (string-match "^\\([^(]+\\)(" x) (match-string 1 x) x)) keyword-spec) if (eq type 'sequence) if (member keyword keywords) return keywords))) (defun chris/org--toggle-inline-images-in-subtree (&optional beg end refresh) "Refresh inline image previews in the current heading/tree." (let ((beg (or beg (if (org-before-first-heading-p) (line-beginning-position) (save-excursion (org-back-to-heading) (point))))) (end (or end (if (org-before-first-heading-p) (line-end-position) (save-excursion (org-end-of-subtree) (point))))) (overlays (cl-remove-if-not (lambda (ov) (overlay-get ov 'org-image-overlay)) (ignore-errors (overlays-in beg end))))) (dolist (ov overlays nil) (delete-overlay ov) (setq org-inline-image-overlays (delete ov org-inline-image-overlays))) (when (or refresh (not overlays)) (org-display-inline-images t t beg end) t))) #+end_src ** MU4E #+begin_src emacs-lisp (use-package mu4e :ensure nil :config (setq mail-user-agent 'mu4e-user-agent) (setq mu4e-maildir "~/Maildir" user-full-name "Chris Cochrun" mu4e-change-filenames-when-moving t mu4e-get-mail-command "mbsync -a" mu4e-update-interval (* 15 60) mu4e-attachment-dir "/home/chris/Documents/PersonalImportant/attachments" mu4e-completing-read-function #'completing-read) (setq mu4e-contexts (list (make-mu4e-context :name "office" :match-func (lambda (msg) (when msg (string-prefix-p "/office" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "chris@tfcconnection.org") (mu4e-sent-folder . "/office/Sent Items/") (mu4e-drafts-folder . "/office/Drafts") (mu4e-trash-folder . "/office/Deleted Items") (mu4e-refile-folder . "/office/Archive") (smtpmail-smtp-user . "chris@tfcconnection.org") (mu4e-compose-signature . "---\nChris Cochrun"))) (make-mu4e-context :name "outlook" :match-func (lambda (msg) (when msg (string-prefix-p "/outlook" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "chris.cochrun@outlook.com") (mu4e-sent-folder . "/outlook/Sent/") (mu4e-drafts-folder . "/outlook/Drafts") (mu4e-trash-folder . "/outlook/Deleted") (mu4e-refile-folder . "/outlook/Archive") (smtpmail-smtp-user . "chris.cochrun@outlook.com") (mu4e-compose-signature . "---\nChris Cochrun"))) (make-mu4e-context :name "gmail" :match-func (lambda (msg) (when msg (string-prefix-p "/gmail" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "ccochrun21@gmail.com") (mu4e-sent-folder . "/gmail/[Gmail].Sent Mail/") (smtpmail-smtp-user . "ccochrun21@gmail.com") (mu4e-compose-signature . "---\nChris Cochrun"))))) ;; Add the ability to send email for o365 (setq message-send-mail-function 'smtpmail-send-it starttls-use-gnutls t smtpmail-starttls-credentials '(("smtp.office365.com" 587 nil nil)) smtpmail-auth-credentials '(("smtp.office365.com" 587 "chris@tfcconnection.org" nil)) smtpmail-default-smtp-server "smtp.office365.com" smtpmail-smtp-server "smtp.office365.com" smtpmail-smtp-service 587) ;; shortcuts in the jumplist by pressing "J" in the mu4e buffer (setq mu4e-maildir-shortcuts '((:maildir "/office/Archive" :key ?a) (:maildir "/office/INBOX" :key ?i) (:maildir "/outlook/INBOX" :key ?l) (:maildir "/office/Junk Email" :key ?j) (:maildir "/office/INBOX/Website Forms" :key ?f) (:maildir "/gmail/INBOX" :key ?g) (:maildir "/office/Sent Items" :key ?s))) ;; (add-to-list mu4e-headers-actions ("org capture message" . mu4e-org-store-and-capture)) (setq mu4e-bookmarks '((:name "Unread messages" :query "flag:unread AND NOT flag:trashed AND NOT maildir:\"/outlook/Junk\" AND NOT maildir:\"/office/Junk Email\" AND NOT maildir:\"/outlook/Deleted\" AND NOT maildir:\"/office/Deleted Items\"" :key 117) (:name "Today's messages" :query "date:today..now" :key 116) (:name "Last 7 days" :query "date:7d..now" :hide-unread t :key 119) (:name "Messages with images" :query "mime:image/*" :key 112))) (setq mu4e-mu-binary "/usr/bin/mu") (setq mu4e-use-fancy-chars t mu4e-headers-draft-mark '("D" . "") mu4e-headers-flagged-mark '("F" . "") mu4e-headers-new-mark '("N" . " ") mu4e-headers-passed-mark '("P" . "") mu4e-headers-replied-mark '("R" . "") mu4e-headers-seen-mark '("S" . " ") mu4e-headers-trashed-mark '("T" . " ") mu4e-headers-attach-mark '("a" . " ") mu4e-headers-encrypted-mark '("x" . "") mu4e-headers-signed-mark '("s" . " ") mu4e-headers-unread-mark '("u" . " ")) (setq mu4e-headers-fields '((:human-date . 12) (:flags . 6) (:from . 22) (:subject))) (setq mu4e-view-actions '(("capture message" . mu4e-action-capture-message) ("view in browser" . mu4e-action-view-in-browser) ("show this thread" . mu4e-action-show-thread))) (defun chris/setup-mu4e-headers () (toggle-truncate-lines +1) (display-line-numbers-mode -1)) (remove-hook 'mu4e-main-mode-hook '(display-line-numbers-mode -1)) (add-hook 'mu4e-headers-mode-hook #'chris/setup-mu4e-headers) (mu4e t) :general (chris/leader-keys :states 'normal :keymaps 'override "om" 'mu4e)) (use-package org-mime :ensure t) #+end_src Let's add org-msg to write emails in org-mode #+begin_src emacs-lisp (use-package org-msg :hook (mu4e-compose-mode . org-msg-edit-mode) :config (org-msg-mode) (setq org-msg-startup "inlineimages" org-msg-greeting-name-limit 3 org-msg-default-alternatives '(html text))) #+end_src ** Calendar #+begin_src emacs-lisp (use-package calfw :commands chris/calfw-calendar-open :config (defun chris/calfw-calendar-open () (interactive) (cfw:open-calendar-buffer :contents-sources (list (cfw:org-create-source "Cyan") ; org-agenda source (cfw:ical-create-source "NV" "https://www.nvhuskies.org/vnews/display.v?ical" "Green") ; School Calendar (cfw:ical-create-source "Outlook" "https://outlook.office365.com/owa/calendar/62a0d491bec4430e825822afd2fd1c01@tfcconnection.org/9acc5bc27ca24ce7a900c57284959f9d8242340735661296952/S-1-8-2197686000-2519837503-3687200543-3873966527/reachcalendar.ics" "Yellow") ; Outlook Calendar ))) :general (chris/leader-keys :states 'normal :keymaps 'override "oc" 'chris/calfw-calendar-open) (general-def cfw:calendar-mode-map "q" 'kill-this-buffer "RET" 'cfw:show-details-command) (general-def 'normal cfw:details-mode-map "q" 'cfw:details-kill-buffer-command)) #+end_src *** Calfw-Org Here we can use org as a way to create calfw sources in the calendar view. #+begin_src emacs-lisp (use-package calfw-org :after calfw) #+end_src *** Calfw-ical Here we setup an easy way to use ical as a source for calendar views. #+begin_src emacs-lisp (use-package calfw-ical :after calfw) #+end_src ** Magit Use magit, because why wouldn't you? duh! #+begin_src emacs-lisp (use-package magit :commands (magit-status magit-get-current-branch) :general (chris/leader-keys :states 'normal :keymaps 'override "g g" 'magit-status) :custom (magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)) #+end_src ** Eshell Let's add our own eshell prompt. and set the password cache to a significantly higher time in order to not need to constantly reuse my password. #+begin_src emacs-lisp (use-package eshell :ensure nil :straight nil :config (require 'em-tramp) (with-eval-after-load 'esh-module ;; REVIEW: It used to work, but now the early `provide' seems to backfire. (unless (boundp 'eshell-modules-list) (load "esh-module")) ;; Don't print the banner. (push 'eshell-tramp eshell-modules-list)) (setq password-cache t password-cache-expiry 3600) (setq eshell-history-size 1024) ;;; Extra execution information (defvar chris/eshell-status-p t "If non-nil, display status before prompt.") (defvar chris/eshell-status--last-command-time nil) (make-variable-buffer-local 'chris/eshell-status--last-command-time) (defvar chris/eshell-status-min-duration-before-display 0 "If a command takes more time than this, display its duration.") (defun chris/eshell-status-display () (if chris/eshell-status--last-command-time (let ((duration (time-subtract (current-time) chris/eshell-status--last-command-time))) (setq chris/eshell-status--last-command-time nil) (when (> (time-to-seconds duration) chris/eshell-status-min-duration-before-display) (format "  %.3fs %s" (time-to-seconds duration) (format-time-string "| %F %T" (current-time))))) (format "  0.000s"))) (defun chris/eshell-status-record () (setq chris/eshell-status--last-command-time (current-time))) (add-hook 'eshell-pre-command-hook 'chris/eshell-status-record) (setq eshell-prompt-function (lambda nil (let ((path (abbreviate-file-name (eshell/pwd)))) (concat (if (or (string= system-name "archdesktop") (string= system-name "syl")) nil (format (propertize "\n(%s@%s)" 'face '(:foreground "#606580")) (propertize (user-login-name) 'face '(:inherit compilation-warning)) (propertize (system-name) 'face '(:inherit compilation-warning)))) (if (and (require 'magit nil t) (or (magit-get-current-branch) (magit-get-current-tag))) (let* ((root (abbreviate-file-name (magit-rev-parse "--show-toplevel"))) (after-root (substring-no-properties path (min (length path) (1+ (length root)))))) (format (propertize "\n[ %s | %s@%s ]" 'face font-lock-comment-face) (propertize root 'face `(:inherit org-warning)) (propertize after-root 'face `(:inherit org-level-1)) (propertize (or (magit-get-current-branch) (magit-get-current-tag)) 'face `(:inherit org-macro)))) (format (propertize "\n[%s]" 'face font-lock-comment-face) (propertize path 'face `(:inherit org-level-1)))) (when chris/eshell-status-p (propertize (or (chris/eshell-status-display) "") 'face font-lock-comment-face)) (propertize "\n" 'face '(:inherit org-todo :weight ultra-bold)) " ")))) ;;; If the prompt spans over multiple lines, the regexp should match ;;; last line only. (setq-default eshell-prompt-regexp "^ ") (setq eshell-destroy-buffer-when-process-dies t) (defun chris/pop-eshell () "Make an eshell frame on the bottom" (interactive) (unless pop-eshell (setq pop-eshell (eshell 100)) (with-current-buffer pop-eshell (eshell/clear-scrollback) (rename-buffer "*eshell-pop*") (display-buffer-in-side-window pop-eshell '((side . bottom)))))) :general (chris/leader-keys :states 'normal :keymaps 'override "oe" 'eshell) (general-def '(normal insert) eshell-mode-map "C-d" 'kill-this-buffer)) #+end_src ** Sly Using sly makes a lot better common-lisp interaction within emacs. #+begin_src emacs-lisp (use-package sly :mode ("\\.lisp\\'" . sly-mode)) #+end_src ** PDF-Tools Let's use pdf-tools for a lot better interaction with pdfs. #+begin_src emacs-lisp (use-package pdf-tools :straight (:host github :repo "flatwhatson/pdf-tools" :branch "fix-macros") :defer 1 :config (pdf-tools-install)) #+end_src ** EPUB #+begin_src emacs-lisp (use-package nov :mode ("\\.epub\\'" . nov-mode) :config (add-hook 'nov-mode-hook 'olivetti-mode)) #+end_src ** Window Management #+begin_src emacs-lisp (setq display-buffer-alist '(("\\*e?shell\\*" (display-buffer-in-side-window) (window-width . 0.4) (side . bottom)) ("*Bongo-Elfeed Queue*" (display-buffer-in-side-window) (window-height . 0.25) (side . bottom)))) #+end_src ** Elfeed #+begin_src emacs-lisp (use-package elfeed :commands (elfeed) :config (defvar chris/elfeed-bongo-playlist "*Bongo-Elfeed Queue*" "Name of the Elfeed+Bongo multimedia playlist.") (defun chris/elfeed-bongo-insert-item () "Insert `elfeed' multimedia links in `bongo' playlist buffer. The playlist buffer has a unique name so that it will never interfere with the default `bongo-playlist-buffer'." (interactive) (let* ((entry (elfeed-search-selected :ignore-region)) (link (elfeed-entry-link entry)) (enclosure (elt (car (elfeed-entry-enclosures entry)) 0)) (url (if (string-prefix-p "https://thumbnails" enclosure) link enclosure)) (title (elfeed-entry-title entry)) (bongo-pl chris/elfeed-bongo-playlist) (buffer (get-buffer-create bongo-pl))) (message "link is %s" link) (message "enclosure is %s" enclosure) (message "url is %s" url) (message "title is %s" title) (elfeed-search-untag-all-unread) (unless (bongo-playlist-buffer) (bongo-playlist-buffer)) (display-buffer buffer) (with-current-buffer buffer (when (not (bongo-playlist-buffer-p)) (bongo-playlist-mode) (setq-local bongo-library-buffer (get-buffer "*elfeed-search*")) (setq-local bongo-enabled-backends '(mpv)) (bongo-progressive-playback-mode)) (goto-char (point-max)) (bongo-insert-uri url (format "%s ==> %s" title url)) (let ((inhibit-read-only t)) (delete-duplicate-lines (point-min) (point-max))) (bongo-recenter)) (message "Enqueued %s “%s” in %s" (if enclosure "podcast" "video") (propertize title 'face 'italic) (propertize bongo-pl 'face 'bold)))) (defun chris/elfeed-bongo-switch-to-playlist () (interactive) (let* ((bongo-pl chris/elfeed-bongo-playlist) (buffer (get-buffer bongo-pl))) (if buffer (switch-to-buffer buffer) (message "No `bongo' playlist is associated with `elfeed'.")))) :general (chris/leader-keys :states 'normal :keymaps 'override "of" 'elfeed) (general-def 'normal elfeed-search-mode-map "v" 'chris/elfeed-bongo-insert-item "h" 'chris/elfeed-bongo-switch-to-playlist)) #+end_src #+begin_src emacs-lisp (use-package elfeed-org :after elfeed :config (setq rmh-elfeed-org-files (list "~/org/elfeed.org")) (elfeed-org) (elfeed-update)) #+end_src ** Bongo #+begin_src emacs-lisp (use-package bongo :commands (bongo bongo-playlist-buffer) :config (define-bongo-backend mpv :program-name 'mpv :constructor 'bongo-start-mpv-player :extra-program-arguments '("--input-ipc-server=/tmp/mpvsocket") :matcher '((local-file "file:" "http:" "ftp:" "lbry:") "ogg" "flac" "mp3" "mka" "wav" "wma" "mpg" "mpeg" "vob" "avi" "ogm" "opus" "mp4" "mkv" "mov" "asf" "wmv" "rm" "rmvb" "ts") :matcher '(("mms:" "mmst:" "rtp:" "rtsp:" "udp:" "unsv:" "dvd:" "vcd:" "tv:" "dvb:" "mf:" "cdda:" "cddb:" "cue:" "sdp:" "mpst:" "tivo:") . t) :matcher '(("http:" "https:" "lbry:") . t)) (setq bongo-enabled-backends '(mpv) bongo-track-mark-icon-file-name "track-mark-icon.png") (defun chris/bongo-mark-line-forward () (interactive) (bongo-mark-line) (goto-char (bongo-point-after-object)) (next-line)) (defun chris/bongo-mpv-pause/resume () (interactive) (bongo-mpv-player-pause/resume bongo-player)) :general (chris/leader-keys :states 'normal :keymaps 'override "ob" 'bongo) (general-def 'normal bongo-playlist-mode-map "RET" 'bongo-play "d" 'bongo-kill-line "u" 'bongo-unmark-region "p" 'bongo-pause/resume "H" 'bongo-switch-buffers "m" 'chris/bongo-mark-line-forward)) #+end_src ** Transmission I use transmission on a server to manage my torrents #+begin_src emacs-lisp (use-package transmission :commands (transmission) :config (setq transmission-host "192.168.1.7" transmission-rpc-path "/transmission/rpc" transmission-refresh-modes '(transmission-mode transmission-files-mode transmission-info-mode transmission-peers-mode)) :general (chris/leader-keys :states 'normal :keymaps 'override "ot" 'transmission)) #+end_src ** Garbage Collection We set the =gc-cons-threshold= variable to really high, now lets set it back low to make sure emacs performs properly. #+begin_src emacs-lisp (setq gc-cons-threshold 2000000) (setq garbage-collection-messages nil) #+end_src * Early Init :PROPERTIES: :header-args: emacs-lisp :tangle early-init.el :END: 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. #+begin_src emacs-lisp :tangle early-init.el ;;; 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 50000000) (message "set gc-cons-threshold to 50000000") ;; ;; 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) #+end_src