emacs/README.org
2024-08-28 13:29:07 -05:00

5606 lines
194 KiB
Org Mode
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#+TITLE: Chris's Personal Emacs Config
#+AUTHOR: Chris Cochrun
From: Chris Cochrun <chris@tfcconnection.org>
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]]
- [[#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)
(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)
(setq-default indent-tabs-mode nil)
(setq comp-deferred-compilation-deny-list nil)
(setq frame-resize-pixelwise 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 ~<escape>~ works as the same as ~<C-g>~
#+begin_src emacs-lisp
(global-set-key (kbd "<escape>") '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)
(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
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")
(setenv "NIX_CONF_DIR" "/etc/nix")
(setenv "NIX_REMOTE" "daemon")
(setenv "XCURSOR_THEME" "phinger-cursors-light")
(setenv "QT_SCALE_FACTOR" "1")
(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" (if (string= (getenv "XDG_CURRENT_DESKTOP") "Hyprland")
"wayland-1"
"wayland-0"))
#+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")))
;; <> <!-- <|> <: <~ <~> <~~ <+ <* <$ </ <+> <*>
;; <$> </> <| <|| <||| <|||| <- <-| <-<<-|-> <->>
;; <<-> <= <=> <<==<<==>=|=>==/==//=!==:=>
;; << <<< <<<<
("<" (rx (+ (or "\+" "\*" "\$" "<" ">" ":" "~" "!"
"-" "/" "|" "="))))
;; >: >- >>- >--|-> >>-|-> >= >== >>== >=|=:=>>
;; >> >>> >>>>
(">" (rx (+ (or ">" "<" "|" "/" ":" "=" "-"))))
;; #: #= #! #( #? #[ #{ #_ #_( ## ### #####
("#" (rx (or ":" "=" "!" "(" "\?" "\[" "{" "_(" "_"
(+ "#"))))
;; ~~ ~~~ ~= ~- ~@ ~> ~~>
("~" (rx (or ">" "=" "-" "@" "~>" (+ "~"))))
;; __ ___ ____ _|_ __|____|_
("_" (rx (+ (or "_" "|"))))
;; Fira code: 0xFF 0x12
;; ("0" (rx (and "x" (+ (in "A-F" "a-f" "0-9")))))
;; Fira code:
;; "Fl" "Tl" "fi" "fj" "fl" "ft"
;; The few not covered by the regexps.
"{|" "[|" "]#" "(*" "}#" "$>" "^="))
;; Enables ligature checks globally in all buffers. You can also do it
;; per mode with `ligature-mode'.
(global-ligature-mode t))
#+end_src
** Keybindings
:PROPERTIES:
:ID: 20230626T163736.174293
:END:
There are two major packages we need to get the functionality I desire. Evil and general.
*** Evil
#+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
evil-undo-system 'undo-redo
scroll-conservatively 101
hscroll-margin 2
scroll-margin 0
scroll-preserve-screen-position t
hscroll-step 1
evil-vsplit-window-right t)
:config
(evil-mode +1))
#+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 evil-escape
:after evil
:init (evil-escape-mode +1)
:config
(setq evil-escape-key-sequence (kbd "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
*** General
:PROPERTIES:
:ID: 20231122T062139.870235
:END:
#+begin_src emacs-lisp
(use-package general
:init
(general-evil-setup)
:config
(defun chris/edit-emacs-config ()
"open the emacs config to edit"
(interactive)
(find-file (expand-file-name "README.org" user-emacs-directory)))
(defun chris/find-videos ()
(interactive)
(find-file (expand-file-name "vids" "~")))
(defun chris/open-bible ()
"find a bible to open"
(interactive)
(find-file "~/docs/bibles/esv.org")
)
(defun chris/transpose-lines (arg)
"Moves the current line or region and follows"
(interactive)
(cond
((and mark-active transient-mark-mode)
(if (> (point) (mark))
(exchange-point-and-mark))
(let ((column (current-column))
(text (delete-and-extract-region (point) (mark))))
(forward-line arg)
(move-to-column column t)
(set-mark (point))
(insert text)
(exchange-point-and-mark)
(setq deactivate-mark nil)))
(t
(beginning-of-line)
(when (or (> arg 0) (not (bobp)))
(forward-line)
(when (or (< arg 0) (not (eobp)))
(transpose-lines arg))
(forward-line -1)))))
(defun chris/transpose-lines-up ()
"Moves the lines up"
(interactive)
(transpose-lines 1)
(forward-line -2)
(indent-according-to-mode))
(defun chris/transpose-lines-down ()
"Moves the lines down"
(interactive)
(forward-line 1)
(transpose-lines 1)
(forward-line -1)
(indent-according-to-mode))
(defun chris/find-todo ()
"opens my todo file"
(interactive)
(find-file "/home/chris/docs/todo/todo.org"))
(general-create-definer chris/leader-keys
:keymaps '(normal visual emacs)
:prefix "SPC")
(chris/leader-keys
:states 'visual
:keymaps 'override
"d" '(execute-extended-command :which-key "execute command"))
(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")
"i" '(:ignore t :which-key "insert")
"o" '(:ignore t :which-key "open")
"p" '(:ignore t :which-key "project")
"oa" '(:ignore t :which-key "org agenda")
"of" '(:ignore t :which-key "elfeed")
"h" '(:ignore t :which-key "help")
"n" '(:ignore t :which-key "notes")
"m" '(:ignore t :which-key "music")
"l" '(:ignore t :which-key "lsp")
"x" '(:ignore t :which-key "guix")
"v" '(:ignore t :which-key "mpv")
"xx" '(guix :which-key "guix")
"sp" '(:ignore t :which-key "passwords")
"bs" '(consult-buffer :which-key "buffer search")
"bd" '(kill-current-buffer :which-key "kill buffer")
"bi" '(ibuffer :which-key "ibuffer")
"tt" '(consult-theme :which-key "choose theme")
"tl" '(toggle-truncate-lines :which-key "truncate lines")
"ts" '(ispell :which-key "spell check")
"ff" '(find-file :which-key "find file")
"fv" '(chris/find-videos :which-key "find file")
"fb" '(chris/open-bible :which-key "find bible book")
"fr" '(consult-recent-file :which-key "recent file")
"fs" '(save-buffer :which-key "save")
"ft" '(chris/find-todo :which-key "open todo file")
"fE" '(consult-file-externally :which-key "find file externally")
"fe" '(chris/edit-emacs-config :which-key "open config")
"hf" '(helpful-function :which-key "describe-function")
"hv" '(helpful-variable :which-key "describe-variable")
"hk" '(helpful-key :which-key "describe-key")
"hF" '(describe-face :which-key "describe-face")
"hb" '(general-describe-keybindings :which-key "describe-bindings")
"hi" '(info :which-key "info manual")
"ht" '(which-key-show-top-level :which-key "show top-level keybindings")
"ss" '(consult-line :which-key "consult search")
"sr" '(consult-ripgrep :which-key "consult ripgrep")
"sd" '(dictionary-search :which-key "search the dictionary")
"sv" '(org-bible-jump :which-key "imenu")
"oP" '(proced :which-key "proced")
"ov" '(vterm :which-key "vterm")
"wo" '(other-window :which-key "other window")
"wn" '(evil-window-next :which-key "other window")
"wm" '(maximize-window :which-key "maximize 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")
"wj" '(evil-window-down :which-key "down window")
"wk" '(evil-window-up :which-key "up window")
"wh" '(evil-window-left :which-key "left window")
"wl" '(evil-window-right :which-key "right window")
"wH" '(evil-window-move-far-left :which-key "right window")
"wK" '(evil-window-move-very-top :which-key "right window")
"wJ" '(evil-window-move-very-bottom :which-key "right window")
"wL" '(evil-window-move-far-right :which-key "right window")
";" '(execute-extended-command :which-key "execute command")
"d" '(execute-extended-command :which-key "execute command")
"&" '(async-shell-command :which-key "async shell command")
":" '(eval-expression :which-key "evaluate expression")
"gc" '(project-compile :which-key "compile project"))
(chris/leader-keys
:states 'normal
:keymaps 'lisp-mode
"e" '(sly-eval-defun :which-key "evaluate top level"))
(general-def 'minibuffer-local-map
"C-v" 'evil-paste-after)
(general-def 'normal
"gcc" 'comment-line
"K" 'helpful-at-point
"C-+" 'text-scale-increase
"C-_" 'text-scale-decrease
"C-S-l" 'evil-window-increase-width
"C-S-j" 'evil-window-decrease-height
"C-S-k" 'evil-window-increase-height
"C-S-h" 'evil-window-decrease-width
"zi" 'text-scale-increase
"zd" 'text-scale-decrease)
(general-def 'normal
:states 'normal
:keymaps 'override
"C-M-j" 'chris/transpose-lines-down
"C-M-k" 'chris/transpose-lines-up)
(general-def 'normal Info-mode-map
"RET" 'Info-follow-nearest-node
"p" 'Info-prev
"n" 'Info-next)
(general-def 'normal prog-mode-map
:states 'normal
:keymaps 'override
"go" 'ff-find-other-file
"TAB" 'indent-according-to-mode)
(general-def 'visual prog-mode-map
:states 'visual
:keymaps 'override
"TAB" 'indent-region)
(general-def 'insert prog-mode-map
:states 'insert
:keymaps 'override
"TAB" 'indent-according-to-mode
"C-<tab>" 'indent-for-tab-command))
#+end_src
*** Hydra
I decided to add hydras. Haven't needed them yet, but maybe they'd be helpful in some instances.
#+begin_src emacs-lisp
(use-package hydra)
#+end_src
** Bluetooth
I think I like this interface to controlling bluetooth the most
#+begin_src emacs-lisp
(use-package bluetooth
:after general
:general
(chris/leader-keys
"oT" 'bluetooth-list-devices))
#+end_src
** Org Mode
:PROPERTIES:
:ID: 20240827T103448.834367
:END:
Org-Mode needs to be loaded pretty high in the file so that we are ensuring things get picked up in the correct order. This has been a problem for me in the past so I prefer it right after setting some keybindings and then much later loading things like =roam= =super-agenda= and others.
Let's start by creating a self contained function of what I'd like started on every org buffer.
#+begin_src emacs-lisp
(setq lpr-command "lpr -o sides=two-sided-long-edge -# ")
(defun chris/org-mode-setup ()
(interactive)
;; (org-indent-mode +1)
(toc-org-mode +1)
(smartparens-mode +1)
(visual-fill-column-mode +1)
(display-line-numbers-mode -1)
(variable-pitch-mode +1)
(setq visual-fill-column-width 100
visual-fill-column-center-text t)
;;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-date
org-verbatim))
(set-face-attribute `,face nil :inherit 'fixed-pitch))
;; changing height and boldness
(dolist (face
'((org-level-1 1.4 ultra-bold)
(org-level-2 1.2 extra-bold)
(org-level-3 1.1 bold)
(org-level-4 1.0 semi-bold)
(org-level-5 1.0 normal)
(org-column 1.0 normal)))
(set-face-attribute (nth 0 face) nil :weight (nth 2 face) :height (nth 1 face)))
;; changing colors
(dolist (face
'((outline-2 "#5af78e")
(outline-3 "#ffb86c")
(outline-4 "#f3f99d")))
(set-face-attribute (nth 0 face) nil :foreground (nth 1 face)))
(set-face-attribute 'org-block-end-line nil :inherit 'org-block-begin-line)
(set-face-attribute 'org-column nil :background "#242631" :inherit 'fixed-pitch)
(set-face-attribute 'org-quote nil :background "#242631" :inherit 'fixed-pitch)
(set-face-attribute 'org-table nil :inherit 'fixed-pitch)
;; Setup better completion functions for org mode
(setq-local completion-at-point-functions
(list (cape-capf-super #'cape-elisp-block #'cape-dabbrev #'cape-dict)))
(tempel-setup-capf))
(defun chris/org-convert-csv-table (beg end)
(interactive (list (mark) (point)))
(org-table-convert-region beg end ","))
(defun chris/org-cycle-hide-drawers (state)
"Re-hide all drawers after a visibility state change."
(interactive)
(when (and (derived-mode-p 'org-mode)
(not (memq state '(overview folded contents))))
(save-excursion
(let* ((globalp (memq state '(contents all)))
(beg (if globalp
(point-min)
(point)))
(end (if globalp
(point-max)
(if (eq state 'children)
(save-excursion
(outline-next-heading)
(point))
(org-end-of-subtree t)))))
(goto-char beg)
(while (re-search-forward org-drawer-regexp end t)
(save-excursion
(beginning-of-line 1)
(when (looking-at org-drawer-regexp)
(let* ((start (1- (match-beginning 0)))
(limit
(save-excursion
(outline-next-heading)
(point)))
(msg (format
(concat
"org-cycle-hide-drawers: "
"`:END:`"
" line missing at position %s")
(1+ start))))
(if (re-search-forward "^[ \t]*:END:" limit t)
(outline-flag-region start (point-at-eol) t)
(user-error msg))))))))))
(defun chris/org-agenda-setup ()
(interactive)
(org-indent-mode +1)
(toc-org-mode +1)
(visual-fill-column-mode +1)
(display-line-numbers-mode -1)
(variable-pitch-mode -1)
(toggle-truncate-lines +1)
(evil-normal-state)
(setq visual-fill-column-width 120
visual-fill-column-center-text t)
(if (string= org-agenda-this-buffer-name "*Org Agenda(fa:area+LEVEL=1)*")
(org-agenda-columns)))
(defun chris/org-mpv (&optional url)
(interactive)
(if (string-empty-p url)
(let (url url-get-url-at-point)
(bongo-insert-uri url (format "%s ==> %s" title url)))
(bongo-insert-uri url (format "%s ==> %s" title url))))
(defun chris/bible-imenu ()
"This is a special call on consult-imenu that will utilize
orderless with a better dispatcher so that we can find our verses
much faster. The hope is to also make this a faster version of imenu."
(interactive)
(consult-imenu))
#+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-indent-turns-on-hiding-stars nil
org-indent-mode-turns-on-hiding-stars nil
org-agenda-sticky t
org-fontify-quote-and-verse-blocks t
org-src-preserve-indentation t
org-src-window-setup 'other-window
org-export-with-broken-links t
org-agenda-current-time-string "⭠ now ────────────────"
org-log-into-drawer t
org-latex-active-timestamp-format "\\textit{%s}"
org-enforce-todo-dependencies t
org-export-preserve-breaks t
org-directory "~/docs/notes")
(add-hook 'org-mode-hook 'chris/org-mode-setup)
(setq org-refile-targets '((org-agenda-files :maxlevel . 6) ("/home/chris/docs/todo/archive.org" :maxlevel . 6)))
(org-babel-do-load-languages 'org-babel-load-languages
'((emacs-lisp . t)
(python . t)
(ditaa . t)
(shell . t)))
(setq org-ditaa-jar-path "/usr/bin/ditaa")
(require 'org-tempo)
(add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
(add-to-list 'org-structure-template-alist '("cl" . "src common-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"))
(add-to-list 'org-structure-template-alist '("rc" . "src restclient"))
(setq org-capture-templates
'(("t" "Personal todo" entry
(file "~/docs/todo/todo.org")
(file ".templates/tasks.org") :prepend t)
("n" "Personal notes" plain
(file denote-last-path)
#'denote-org-capture
:no-save t
:immediate-finish nil
:kill-buffer t
:jump-to-captured t)
("p" "TFC Plan" entry
(function chris/denote-capture-lesson-file)
(file ".templates/tfcplantemplate.org")
:prepend nil
:jump-to-captured t
:empty-lines 1)
("l" "TFC Lesson" plain
(function chris/org-capture-denote-file-path)
(file ".templates/lessontemplate.org")
:prepend nil
:jump-to-captured t
:empyt-lines 1)
("P" "TFC Posts" entry
(file+headline "nvtfc_social_media.org" "Posts")
(file ".templates/posts.org")
:prepend t
:jump-to-captured t)
("s" "TFC Supporter" entry
(file "tfc_supporters.org")
(file ".templates/supporter.org")
:prepend t
:jump-to-captured t)
("r" "Templates for projects")
("rt" "Project-local todo" entry
(file+headline chris/project-todo "Tasks")
"* TODO %?\n%a\n%i\n\n" :prepend t)
("rn" "Project-local notes" entry
(file+headline chris/project-todo "Notes")
"* %U %?\n%a\n%i\n\n" :prepend t)
("rc" "Project-local changelog" entry
(file+headline chris/project-changelog "Changelog")
"* %U %?\n%a\n%i\n\n" :prepend t))
org-capture-use-agenda-date t
org-agenda-timegrid-use-ampm t
org-agenda-show-inherited-tags nil
org-agenda-tags-column -100)
(setq org-agenda-prefix-format '((agenda . " %i %?-12t% s")
(todo . " %i %-12:c")
(tags . " %i %-12:c")
(search . " %i %-12:c")))
(setq org-agenda-category-icon-alist
'(("todo" "~/docs/notes/icons/task.png" nil nil :ascent center)
("lesson" "~/docs/notes/icons/book.png" nil nil :ascent center)
("dev" "~/docs/notes/icons/coding.png" nil nil :ascent center)
("TODO" "~/docs/notes/icons/coding.png" nil nil :ascent center)))
(setq org-imenu-depth 4
org-odt-styles-file "/home/chris/docs/style.odt"
org-export-with-toc nil
org-export-with-author t
org-export-with-section-numbers nil
org-todo-keywords
'((sequence "TODO(t)" "PROJ(p)" "STRT(s)" "WAIT(w)" "HOLD(h)" "|" "DONE(d)" "CNCL(c)")
(sequence "[ ](T)" "[-](S)" "[?](W)" "|" "[X](D)"))
org-agenda-files
'("~/docs/todo/todo.org"
"~/dev/lumina/TODO.org"
"~/dev/tfcconnection/TODO.org"
"~/docs/notes/lessons/"
"~/docs/notes/20230919T090157--house-renovation__area.org")
org-id-method 'ts
org-agenda-tags-column -75
org-agenda-dim-blocked-tasks nil
org-columns-summary-types '(("+" . org-columns--summary-sum)
("$" . org-columns--summary-currencies)
("X" . org-columns--summary-checkbox)
("X/" . org-columns--summary-checkbox-count)
("X%" . org-columns--summary-checkbox-percent)
("max" . org-columns--summary-max)
("mean" . org-columns--summary-mean)
("min" . org-columns--summary-min)
(":" . org-columns--summary-sum-times)
(":max" . org-columns--summary-max-time)
(":mean" . org-columns--summary-mean-time)
(":min" . org-columns--summary-min-time)
("@max" . org-columns--summary-max-age)
("@mean" . org-columns--summary-mean-age)
("@min" . org-columns--summary-min-age)
("est+" . org-columns--summary-estimate)))
(setq org-agenda-custom-commands
'(("w" todo "WAIT")
("f" . "Second brain")
("fa" tags "area+LEVEL=1")
("fr" tags "resource+LEVEL=1")
("fp" . "Projects")
("fpp" "All projects" tags-todo "project+LEVEL=1")
("fpt" "All TFC projects" tags-todo "project+tfc+LEVEL=1")
("fpd" "All Dev projects" tags-todo "project+dev")
("fA" tags "archive+LEVEL=1")
("h" . "Family searches")
("ha" tags "abbie")
("hl" tags "luke")
("hy" tags "ty")
("hj" tags "josiah")))
(defun chris/org-columns-view ()
"Turn on org-columns overlay and turn off olivetti-mode"
(interactive)
(goto-char (point-min))
(org-content)
(org-columns)
(setq visual-fill-column-width 150))
(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)
(add-hook 'org-agenda-finalize-hook 'chris/org-agenda-setup)
(advice-add 'org-agenda-todo :after #'org-save-all-org-buffers)
(setq org-refile-targets '((org-agenda-files . (:maxlevel . 6))))
(setq org-agenda-window-setup 'current-window)
(defun chris/org-agenda ()
"create a window that houses my org-agenda"
(interactive)
(with-selected-frame (make-frame
'((name . "org-agenda")
(width . 100)))
(org-agenda-list)))
(setq org-latex-packages-alist '(("margin=2cm" "geometry" nil)))
(setq org-latex-title-command "\\maketitle\\vspace{-4em}")
(setq org-publish-project-alist
`(("home"
:base-directory "~/docs/notes/site/content"
:base-extension "org"
:recursive nil
:html-doctype "html5"
:html-html5-fancy t
:html-self-link-headlines t
:html-head "<link rel=\"stylesheet\" href=\"pico.css\" type=\"text/css\"/>"
:publishing-directory "~/docs/notes/site/public/"
:publishing-function org-html-publish-to-html)
("posts"
:base-directory "~/docs/notes/site/content/posts"
:base-extension "org"
:recursive t
:html-doctype "html5"
:html-html5-fancy t
:html-self-link-headlines t
:htmlized-source t
:html-head "<link rel=\"stylesheet\" href=\"../pico.css\" type=\"text/css\"/>"
:publishing-directory "~/docs/notes/site/public/content/"
:publishing-function org-html-publish-to-html)
("static"
:base-directory "~/docs/notes/site/assets/"
:base-extension "css\\|txt\\|jpg\\|gif\\|png"
:recursive t
:html-doctype "html5"
:html-html5-fancy t
:publishing-directory "~/docs/notes/site/public/"
:publishing-function org-publish-attachment)
("cochrun.xyz" :components ("home" "posts" "static"))))
(setq org-html-postamble t
org-html-postamble-format '(("en"
"<footer> <p>Author: %a (%e)</p></footer>")))
(defun chris/org-cycle-hide-drawers-all ()
(interactive)
(chris/org-cycle-hide-drawers 'all))
(defun chris/org-cycle-hide-drawers-sub ()
(interactive)
(chris/org-cycle-hide-drawers 'subtree))
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"c" 'org-capture
"rr" 'org-refile
"E" 'org-export-dispatch
"oa" 'org-agenda
"gt" 'org-babel-tangle
"td" 'org-toggle-link-display
"tp" 'org-toggle-pretty-entities
"il" 'org-insert-link
"it" 'org-insert-todo-subheading)
(chris/leader-keys
:states 'normal
:keymaps 'org-mode-map
"is" 'org-time-stamp
"ta" 'chris/org-cycle-hide-drawers-all
"tp" (chris/org-cycle-hide-drawers 'children)
"th" 'chris/org-cycle-hide-drawers-sub
"iw" 'org-web-tools-insert-web-page-as-entry)
(chris/leader-keys
:states 'visual
:keymaps 'override
"il" 'org-insert-link)
('normal org-agenda-mode-map
"q" 'org-agenda-quit
"r" 'org-agenda-redo
"d" 'org-agenda-deadline
"s" 'org-agenda-schedule
"t" 'org-agenda-todo
"c" 'org-agenda-capture)
('normal org-columns-map
"j" 'outline-next-heading
"h" 'outline-previous-heading
"q" 'chris/org-columns-quit)
('normal org-mode-map
"RET" '+org/dwim-at-point
"gC" 'chris/org-columns-view
"ge" 'org-edit-src-code
"gr" 'revert-buffer
"gt" 'org-set-tags-command
"ze" 'org-emphasize
"zn" 'org-narrow-to-subtree
"zh" (chris/org-cycle-hide-drawers 'subtree)
"zw" 'widen
"zp" 'org-set-property
"S" 'org-schedule
"t" 'org-todo
"gf" 'org-footnote-action
"gl" 'org-id-copy
"gk" 'languagetool-correct-at-point
"C-M-k" 'org-priority-up
"C-M-j" 'org-priority-down
"gs" 'org-time-stamp)
('visual org-mode-map
"gf" 'org-footnote-action
"gm" 'org-emphasize)
('insert org-mode-map
"C-i" 'completion-at-point)
('normal 'org-src-mode-map
"q" 'org-edit-src-abort))
#+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 =denote= 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/denote-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 +1))
(let* ((files (denote-directory-files))
(lessons (remove nil
(cl-loop for file in files
collect (if (string-match "lessons/" file) file))))
(lesson (completing-read "Select Lesson: " lessons nil nil '(".*_lesson" . 0))))
(find-file lesson)
(goto-char (point-min))
(search-forward "* PLAN")))
(defun chris/org-capture-denote-file-path ()
"Function returning the file placement using denote for capture"
(interactive)
(denote-subdirectory)
(save-buffer))
#+end_src
#+begin_src emacs-lisp
(defun chris/project-todo ()
(concat (projectile-project-root) "TODO.org"))
(defun chris/project-changelog ()
(concat (projectile-project-root) "CHANGELOG.org"))
#+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 "README.org" user-emacs-directory))
(let ((org-confirm-babel-evaluate nil))
(org-babel-tangle))))
(add-hook 'after-save-hook #'chris/org-babel-tangle-config)
#+end_src
We also need to add =evil-org= to make better keybindings.
#+begin_src emacs-lisp
(use-package evil-org
:ensure t
:after org
:hook (org-mode . (lambda () evil-org-mode))
:config
;; (add-to-list 'evil-digit-bound-motions 'evil-org-beginning-of-line)
;; (evil-define-key 'motion 'evil-org-mode
;; (kbd "0") 'evil-org-beginning-of-line)
(require 'evil-org-agenda)
(evil-org-agenda-set-keys))
#+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
:init
(setq org-super-agenda-groups '((:name "📅 Today"
:time-grid t
:scheduled today)
(:name "Due Today"
:deadline today)
(:name "Important"
:priority "A")
(:name "Development"
:category "TODO"
:category "dev"
:scheduled nil)
(:name "Overdue"
:time-grid t
:scheduled past
:deadline past)
(:name "Due soon"
:deadline future)))
:config
(org-super-agenda-mode +1)
(setq org-super-agenda-header-map nil))
#+end_src
*** Org-Roam
:PROPERTIES:
:ID: 20230828T104826.746650
:END:
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 :tangle no
(use-package org-roam
:after org
:ensure t
:init
(setq org-roam-v2-ack t)
:config
(setq org-roam-directory "~/docs/notes"
org-roam-buffer-width 0.25
org-roam-list-files-commands '(fd rg find fdfind)
org-roam-file-exclude-regexp '(".stversion/|logseq/|presentations/|.stfolder/|.*~.*|.*sync.*|bibles/")
org-roam-db-location "~/.emacs.d/org-roam.db"
org-roam-completion-everywhere t
org-roam-dailies-directory "dailies/"
org-roam-capture-templates
'(("d" "default" entry "\n* %?"
:if-new (file+head "${slug}.org"
"#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n#+CATEGORY: todo\n\n%a")
:unnarrowed t)
("b" "bible" entry "\n* %?"
:if-new (file+head "${slug}.org"
"#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n#+CATEGORY: bible\n- tags :biblestudy:%^G\n\n")
:unnarrowed t)
("l" "TFC Lesson" plain (file ".templates/lessontemplate.org")
:if-new (file+head "lessons/${slug}.org"
"#+TITLE: ${title}\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n#+CATEGORY: lesson\n")
:unnarrowed t))
org-roam-dailies-capture-templates
'(("d" "daily" plain "%?"
:immediate-finish nil
: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?"
:target (file+head "%<%Y-%m-%d>.org"
"#+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?\n* Bible")
:unnarrowed t
)
("b" "biblical daily" plain "%?"
:file-name "%<%Y-%m-%d>-bib"
:target (file+head "%<%Y-%m-%d>-bib.org" "#+TITLE: %<%Y-%m-%d> - Biblical\n#+AUTHOR: Chris Cochrun\n#+CREATED: %<%D - %I:%M %p>\n\n* Notes")
:unnarrowed t)
("m" "meeting" plain "%?"
:file-name "%<%Y-%m-%d>-meeting-${slug}"
:target (file+head "%<%Y-%m-%d>-meeting-${slug}.org" "#+TITLE: %<%Y-%m-%d> - ${slug}
#+AUTHOR: Chris Cochrun
#+CREATED: %<%D - %I:%M %p>
* Attendees
-
* Notes")
:unnarrowed t)))
(setq org-roam-node-display-template
(concat "${title:*} "
(propertize "${tags:10}" 'face 'org-tag)))
(defun chris/org-roam-node-create ()
"Create an org roam node underneath this org node"
(interactive)
(+org/insert-item-below 1)
(org-id-get-create)
(org-roam-alias-add))
(defun chris/org-roam-node-id-create ()
"Make the basic org node and org roam one by adding an id and creating an alias"
(interactive)
(org-id-get-create)
(org-roam-alias-add))
;; (set-face-attribute 'magit-section-highlight nil :inherit 'variable-pitch)
(defun chris/consult-ripgrep-files-with-matches (&optional dir initial)
"Use consult-find style to return matches with \"rg --file-with-matches \". No live preview."
(interactive "P")
(let ((consult-find-command "rg --null --ignore-case -g presentations --type org --max-columns=500 --no-heading --line-buffered --line-number . -e ARG OPTS"))
(consult-find dir initial)))
(defun chris/org-roam-rg-search ()
"Search org-roam directory using consult-ripgrep. With live-preview."
(interactive)
(let ((consult-ripgrep-command "rg --null --ignore-case --type org --line-buffered --color=always --max-columns=500 --no-heading --line-number . -e ARG OPTS"))
(consult-ripgrep org-roam-directory)))
(defun chris/org-roam-rg-file-search ()
"Search org-roam directory using consult-find with \"rg --file-with-matches \". No live preview."
(interactive)
(chris/consult-ripgrep-files-with-matches org-roam-directory))
(defun chris/org-roam-refile-node-to-file ()
"Take the node at point and refile it into a new org roam file"
(interactive)
(if (org-before-first-heading-p)
(message "Not in or on an org heading")
(save-excursion
;; If inside heading contents, move the point back to the heading
;; otherwise `org-agenda-get-some-entry-text' won't work.
(unless (org-on-heading-p) (org-previous-visible-heading 1))
(let ((heading (substring-no-properties (thing-at-point 'line)))
(contents (substring-no-properties
(org-agenda-get-some-entry-text
(point-marker)
most-positive-fixnum)))
(node (concat heading contents)))
(message "Copied: %s" heading)
(kill-new node)
(org-roam-node-find)
(delete-line)
(newline)
(yank)))))
(org-roam-setup)
:general
(chris/leader-keys
:states '(normal visual)
:keymaps 'override
"nf" '(org-roam-node-find :which-key "org roam ff")
"nr" 'org-roam-buffer-toggle
"ni" 'chris/org-roam-node-create
"nl" 'org-roam-node-insert
"nc" 'org-roam-capture
"nt" 'org-roam-dailies-goto-today
"ng" 'org-roam-graph
"na" 'org-roam-alias-add
"in" 'org-roam-node-insert
"ns" 'chris/org-roam-rg-search
"nA" 'chris/org-roam-node-id-create)
(chris/leader-keys
:states 'visual
:keymaps 'override
"in" 'org-roam-node-insert))
#+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 websocket)
(use-package org-roam-ui
:after org-roam
:config
(setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
#+END_SRC
*** Denote
:PROPERTIES:
:ID: 20231224T062442.525926
:END:
I might try switching to and using denote instead of Org Roam. Denote doesn't use a DB and instead uses a clever naming scheme to make sure that all files are connectable. The one downside of denote is that the links are specific to denote instead of plain org links.
#+begin_src emacs-lisp
(use-package denote
:config
(require 'denote-org-extras)
(setq denote-directory "/home/chris/docs/notes"
denote-dired-directories '("/home/chris/docs/notes" "/home/chris/docs/notes/lessons")
denote-dired-directories-include-subdirectories t
denote-modules '(project xref ffap)
denote-known-keywords '("emacs" "bible" "jesus" "tfc" "lesson" "it" "dev"))
(setq denote-rename-buffer-format "[D] %>t")
(denote-rename-buffer-mode +1)
(setq denote-org-front-matter
"#+TITLE: %1$s\n#+AUTHOR: Chris Cochrun\n#+CREATED: %2$s\n#+filetags: %3$s\n#+identifier: %4$s\n")
(add-hook 'dired-mode-hook #'denote-dired-mode-in-directories)
;; (defvar denote-faces--file-name-regexp
;; (concat "\\(?1:[0-9]\\{8\\}\\)\\(?2:T[0-9]\\{6\\}\\)"
;; "\\(?:\\(?3:==\\)\\(?4:[[:alnum:][:nonascii:]=]*?\\)\\)?"
;; "\\(?:\\(?5:--\\)\\(?6:[[:alnum:][:nonascii:]-]*?\\)\\)?"
;; "\\(?:\\(?7:__\\)\\(?8:[[:alnum:][:nonascii:]_-]*?\\)\\)?"
;; "\\(?9:\\..*\\)?$")
;; "Regexp of file names for fontification.")
;; (defconst denote-faces-file-name-keywords
;; `((,(concat "[\t\s]+" denote-faces--file-name-regexp)
;; (1 'denote-faces-date)
;; (2 'denote-faces-time)
;; (3 'denote-faces-delimiter nil t)
;; (4 'denote-faces-signature nil t)
;; (5 'denote-faces-delimiter nil t)
;; (6 'denote-faces-title nil t)
;; (7 'denote-faces-delimiter nil t)
;; (8 'denote-faces-keywords nil t)
;; (9 'denote-faces-extension nil t )))
;; "Keywords for fontification of file names.")
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"nf" 'denote-open-or-create
"nb" 'denote-backlinks
"nk" 'denote-keywords-add
"nl" 'denote-link-or-create))
#+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 :tangle no
(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 -1)))
#+end_src
*** Org-Present
#+begin_src emacs-lisp
(use-package org-present
:commands org-present
:config
(add-hook 'org-present-mode-hook
(lambda ()
(org-present-big)
(org-present-hide-cursor)))
(add-hook 'org-present-mode-quit-hook
(lambda ()
(org-present-small)
(org-present-show-cursor)))
:general
(chris/leader-keys 'normal
"oo" 'org-present)
('normal org-present-mode-map
"q" 'org-present-quit
"j" 'org-present-next
"k" 'org-present-prev
))
#+end_src
*** Org Modern
#+BEGIN_SRC emacs-lisp
(use-package org-modern
:config
(setq org-modern-timestamp nil
org-modern-table nil
org-modern-star '("" "" "" "" ""))
(custom-set-faces
'(org-modern-tag ((t :background "#9aedfe" :foreground "#282a36")))
)
(global-org-modern-mode +1)
)
#+END_SRC
*** Org Additions from Doom Emacs
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
;;; lang/org/autoload/org.el -*- lexical-binding: t; -*-
;;
;;; Helpers
(defun +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)
(save-excursion (point-min))
(save-excursion (org-back-to-heading) (point)))))
(end (or end
(if (org-before-first-heading-p)
(save-excursion (org-next-visible-heading 1) (point))
(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)))
(defun +org--insert-item (direction)
(let ((context (org-element-lineage
(org-element-context)
'(table table-row headline inlinetask item plain-list)
t)))
(pcase (org-element-type context)
;; Add a new list item (carrying over checkboxes if necessary)
((or `item `plain-list)
(let ((orig-point (point)))
;; Position determines where org-insert-todo-heading and `org-insert-item'
;; insert the new list item.
(if (eq direction 'above)
(org-beginning-of-item)
(end-of-line))
(let* ((ctx-item? (eq 'item (org-element-type context)))
(ctx-cb (org-element-property :contents-begin context))
;; Hack to handle edge case where the point is at the
;; beginning of the first item
(beginning-of-list? (and (not ctx-item?)
(= ctx-cb orig-point)))
(item-context (if beginning-of-list?
(org-element-context)
context))
;; Horrible hack to handle edge case where the
;; line of the bullet is empty
(ictx-cb (org-element-property :contents-begin item-context))
(empty? (and (eq direction 'below)
;; in case contents-begin is nil, or contents-begin
;; equals the position end of the line, the item is
;; empty
(or (not ictx-cb)
(= ictx-cb
(1+ (point))))))
(pre-insert-point (point)))
;; Insert dummy content, so that `org-insert-item'
;; inserts content below this item
(when empty?
(insert " "))
(org-insert-item (org-element-property :checkbox context))
;; Remove dummy content
(when empty?
(delete-region pre-insert-point (1+ pre-insert-point))))))
;; Add a new table row
((or `table `table-row)
(pcase direction
('below (save-excursion (org-table-insert-row t))
(org-table-next-row))
('above (save-excursion (org-shiftmetadown))
(+org/table-previous-row))))
;; Otherwise, add a new heading, carrying over any todo state, if
;; necessary.
(_
(let ((level (or (org-current-level) 1)))
;; I intentionally avoid `org-insert-heading' and the like because they
;; impose unpredictable whitespace rules depending on the cursor
;; position. It's simpler to express this command's responsibility at a
;; lower level than work around all the quirks in org's API.
(pcase direction
(`below
(let (org-insert-heading-respect-content)
(goto-char (line-end-position))
(org-end-of-subtree)
(insert "\n" (make-string level ?*) " ")))
(`above
(org-back-to-heading)
(insert (make-string level ?*) " ")
(save-excursion (insert "\n"))))
(run-hooks 'org-insert-heading-hook)
(when-let* ((todo-keyword (org-element-property :todo-keyword context))
(todo-type (org-element-property :todo-type context)))
(org-todo
(cond ((eq todo-type 'done)
;; Doesn't make sense to create more "DONE" headings
(car (+org-get-todo-keywords-for todo-keyword)))
(todo-keyword)
('todo)))))))
(when (org-invisible-p)
(org-show-hidden-entry))
(when (and (bound-and-true-p evil-local-mode)
(not (evil-emacs-state-p)))
(evil-insert 1))))
;;;###autoload
(defun +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)))
;;
;;; Modes
;;;###autoload
(define-minor-mode +org-pretty-mode
"Hides emphasis markers and toggles pretty entities."
:init-value nil
:lighter " *"
:group 'evil-org
(setq org-hide-emphasis-markers +org-pretty-mode)
(org-toggle-pretty-entities)
(with-silent-modifications
;; In case the above un-align tables
(org-table-map-tables 'org-table-align t)))
;;
;;; Commands
;;;###autoload
(defun +org/return ()
"Call `org-return' then indent (if `electric-indent-mode' is on)."
(interactive)
(org-return electric-indent-mode))
;;;###autoload
(defun +org/dwim-at-point (&optional arg)
"Do-what-I-mean at point.
If on a:
- checkbox list item or todo heading: toggle it.
- citation: follow it
- headline: cycle ARCHIVE subtrees, toggle latex fragments and inline images in
subtree; update statistics cookies/checkboxes and ToCs.
- clock: update its time.
- footnote reference: jump to the footnote's definition
- footnote definition: jump to the first reference of this footnote
- timestamp: open an agenda view for the time-stamp date/range at point.
- 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.
- src block: execute it
- latex fragment: toggle it.
- link: follow it
- otherwise, refresh all inline images in current tree."
(interactive "P")
(if (button-at (point))
(call-interactively #'push-button)
(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
((or `citation `citation-reference)
(org-cite-follow context arg))
(`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 (+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)))
(+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 arg))
((or `latex-fragment `latex-environment)
(org-latex-preview arg))
(`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)))
(+org--toggle-inline-images-in-subtree
(org-element-property :begin lineage)
(org-element-property :end lineage))
(org-open-at-point arg))))
(`paragraph
(+org--toggle-inline-images-in-subtree))
((guard (org-element-property :checkbox (org-element-lineage context '(item) t)))
(let ((match (and (org-at-item-checkbox-p) (match-string 1))))
(org-toggle-checkbox (if (equal match "[ ]") '(16)))))
(_
(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)
(+org--toggle-inline-images-in-subtree
(org-element-property :begin context)
(org-element-property :end context))))))))
;;;###autoload
(defun +org/shift-return (&optional arg)
"Insert a literal newline, or dwim in tables.
Executes `org-table-copy-down' if in table."
(interactive "p")
(if (org-at-table-p)
(org-table-copy-down arg)
(org-return nil arg)))
;; I use these instead of `org-insert-item' or `org-insert-heading' because they
;; impose bizarre whitespace rules depending on cursor location and many
;; settings. These commands have a much simpler responsibility.
;;;###autoload
(defun +org/insert-item-below (count)
"Inserts a new heading, table cell or item below the current one."
(interactive "p")
(dotimes (_ count) (+org--insert-item 'below)))
;;;###autoload
(defun +org/insert-item-above (count)
"Inserts a new heading, table cell or item above the current one."
(interactive "p")
(dotimes (_ count) (+org--insert-item 'above)))
;;;###autoload
(defun +org/toggle-last-clock (arg)
"Toggles last clocked item.
Clock out if an active clock is running (or cancel it if prefix ARG is non-nil).
If no clock is active, then clock into the last item. See `org-clock-in-last' to
see how ARG affects this command."
(interactive "P")
(require 'org-clock)
(cond ((org-clocking-p)
(if arg
(org-clock-cancel)
(org-clock-out)))
((and (null org-clock-history)
(or (org-on-heading-p)
(org-at-item-p))
(y-or-n-p "No active clock. Clock in on current item?"))
(org-clock-in))
((org-clock-in-last arg))))
;;; Folds
;;;###autoload
(defalias #'+org/toggle-fold #'+org-cycle-only-current-subtree-h)
;;;###autoload
(defun +org/open-fold ()
"Open the current fold (not but its children)."
(interactive)
(+org/toggle-fold t))
;;;###autoload
(defalias #'+org/close-fold #'outline-hide-subtree)
;;;###autoload
(defun +org/close-all-folds (&optional level)
"Close all folds in the buffer (or below LEVEL)."
(interactive "p")
(outline-hide-sublevels (or level 1)))
;;;###autoload
(defun +org/open-all-folds (&optional level)
"Open all folds in the buffer (or up to LEVEL)."
(interactive "P")
(if (integerp level)
(outline-hide-sublevels level)
(outline-show-all)))
(defun +org--get-foldlevel ()
(let ((max 1))
(save-restriction
(narrow-to-region (window-start) (window-end))
(save-excursion
(goto-char (point-min))
(while (not (eobp))
(org-next-visible-heading 1)
(when (outline-invisible-p (line-end-position))
(let ((level (org-outline-level)))
(when (> level max)
(setq max level))))))
max)))
;;;###autoload
(defun +org/show-next-fold-level (&optional count)
"Decrease the fold-level of the visible area of the buffer. This unfolds
another level of headings on each invocation."
(interactive "p")
(let ((new-level (+ (+org--get-foldlevel) (or count 1))))
(outline-hide-sublevels new-level)
(message "Folded to level %s" new-level)))
;;;###autoload
(defun +org/hide-next-fold-level (&optional count)
"Increase the global fold-level of the visible area of the buffer. This folds
another level of headings on each invocation."
(interactive "p")
(let ((new-level (max 1 (- (+org--get-foldlevel) (or count 1)))))
(outline-hide-sublevels new-level)
(message "Folded to level %s" new-level)))
;;
;;; Hooks
;;;###autoload
(defun +org-indent-maybe-h ()
"Indent the current item (header or item), if possible.
Made for `org-tab-first-hook' in evil-mode."
(interactive)
(cond ((not (and (bound-and-true-p evil-local-mode)
(evil-insert-state-p)))
nil)
((and (bound-and-true-p org-cdlatex-mode)
(or (org-inside-LaTeX-fragment-p)
(org-inside-latex-macro-p)))
nil)
((org-at-item-p)
(if (eq this-command 'org-shifttab)
(org-outdent-item-tree)
(org-indent-item-tree))
t)
((org-at-heading-p)
(ignore-errors
(if (eq this-command 'org-shifttab)
(org-promote)
(org-demote)))
t)
((org-in-src-block-p t)
(save-window-excursion
(org-babel-do-in-edit-buffer
(call-interactively #'indent-for-tab-command)))
t)
((and (save-excursion
(skip-chars-backward " \t")
(bolp))
(org-in-subtree-not-table-p))
(call-interactively #'tab-to-tab-stop)
t)))
;;;###autoload
(defun +org-cycle-only-current-subtree-h (&optional arg)
"Toggle the local fold at the point, and no deeper.
`org-cycle's standard behavior is to cycle between three levels: collapsed,
subtree and whole document. This is slow, especially in larger org buffer. Most
of the time I just want to peek into the current subtree -- at most, expand
,*only* the current subtree.
All my (performant) foldings needs are met between this and `org-show-subtree'
(on zO for evil users), and `org-cycle' on shift-TAB if I need it."
(interactive "P")
(unless (or (eq this-command 'org-shifttab)
(and (bound-and-true-p org-cdlatex-mode)
(or (org-inside-LaTeX-fragment-p)
(org-inside-latex-macro-p))))
(save-excursion
(org-beginning-of-line)
(let (invisible-p)
(when (and (org-at-heading-p)
(or org-cycle-open-archived-trees
(not (member org-archive-tag (org-get-tags))))
(or (not arg)
(setq invisible-p (outline-invisible-p (line-end-position)))))
(unless invisible-p
(setq org-cycle-subtree-status 'subtree))
(org-cycle-internal-local)
t)))))
;;;###autoload
(defun +org-make-last-point-visible-h ()
"Unfold subtree around point if saveplace places us in a folded region."
(and (not org-inhibit-startup)
(not org-inhibit-startup-visibility-stuff)
;; Must be done on a timer because `org-show-set-visibility' (used by
;; `org-reveal') relies on overlays that aren't immediately available
;; when `org-mode' first initializes.
(run-at-time 0.1 nil #'org-reveal '(4))))
;;;###autoload
(defun +org-remove-occur-highlights-h ()
"Remove org occur highlights on ESC in normal mode."
(when org-occur-highlights
(org-remove-occur-highlights)
t))
;;;###autoload
(defun +org-enable-auto-update-cookies-h ()
"Update statistics cookies when saving or exiting insert mode (`evil-mode')."
(when (bound-and-true-p evil-local-mode)
(add-hook 'evil-insert-state-exit-hook #'org-update-parent-todo-statistics nil t))
(add-hook 'before-save-hook #'org-update-parent-todo-statistics nil t))
#+end_src
Here we can add those functions to the proper keys in both normal and insert mode.
#+begin_src emacs-lisp
(general-def 'normal org-mode-map
"C-<return>" '+org/insert-item-below)
(general-def 'insert org-mode-map
"C-<return>" '+org/insert-item-below)
#+end_src
#+RESULTS:
: +org-enable-auto-update-cookies-h
*** Org Reveal
:PROPERTIES:
:ID: 20231024T143730.876619
:END:
Org reveal allows me to create beautiful and powerful slideshows using the incredible reveal.js system. Whilst I hate the web making so much into terrible things, I do really like reveal.js. It's honestly a much better picture of what javascript can do than apps like Facebook.
So, how do we use this? Well, let's try like this.
#+begin_src emacs-lisp
(use-package org-re-reveal
:ensure nil
:config
(setq org-re-reveal-root "file:///home/chris/docs/presentations/reveal.js/"
;; org-re-reveal-theme "serif"
org-re-reveal-transition "slide"
org-re-reveal-mobile-app t
org-re-reveal-width "90%")
(add-to-list 'org-re-reveal-plugin-config '(audio-slideshow "RevealAudioSlideshow" "plugin/audio-slideshow/plugin.js"))
(defun chris/org-re-reveal-export-to-html
(&optional async subtreep visible-only body-only ext-plist backend)
"Export current buffer to a reveal.js HTML file with a different name
so that it can exists within a static site showing the file as a document
as well as a presentation.
Optional ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST are passed
to `org-export-to-file'.
Optional BACKEND must be `re-reveal' or a backend derived from it."
(interactive)
(let* ((backend (or backend 're-reveal))
(extension (concat "-presentation" "." org-html-extension))
(client-ext (concat org-re-reveal-multiplex-client-ext extension))
(file (org-export-output-file-name extension subtreep))
(clientfile (org-export-output-file-name client-ext subtreep))
(org-html-container-element "div"))
(setq org-re-reveal-client-multiplex nil)
(org-export-to-file backend file
async subtreep visible-only body-only ext-plist)
;; Export the client HTML file if org-re-reveal-client-multiplex is set true
;; by previous call to org-export-to-file
(if org-re-reveal-client-multiplex
(org-export-to-file backend clientfile
async subtreep visible-only body-only ext-plist))
file)))
#+end_src
*** ox-zola
I'm going to start a website for my own ramblings, tutorials and links. To do this I'll likely use Zola since it's built in Rust.
#+begin_src emacs-lisp
(use-package ox-hugo)
(use-package ox-zola
:load-path "ox-zola/"
:config
(setq org-hugo-base-dir "/home/chris/dev/cochrun.xyz"
org-hugo-section "blog"))
#+end_src
** AI
GPTEL is a package that uses chatGPT to get some text generation in org-mode
*** gptel
#+begin_src emacs-lisp
(use-package gptel
:init
(setq gptel-model "llama3.1:latest"
gptel-backend (gptel-make-ollama "ollama"
:host "ai.tfcconnection.org"
:protocol "https"
:stream t
:models '("llama3.1:latest")))
(setq gptel-directives '((default
. "You are a large language model living in Emacs and a helpful assistant. Respond concisely.")
(programming
. "You are a large language model and a careful programmer. Provide code and only code as output without any additional text, prompt or note.")
(writing
. "You are a large language model and a writing assistant. Respond concisely.")
(chat
. "You are a large language model and a conversation partner. Respond concisely.")
(dnd . "Assume the role of an expert fantasy writer that specializes in interactive fiction, as well as the storyline, characters, locations, descriptions, groups and organizations, stories, events, and magical objects of Baldurs Gate, Tales of the Sword Coast, Baldurs Gate 2: Shadows of Amn, Throne of Bhaal, and Baldurs Gate 3. The adventure takes place on the continent of Faerun.
Describe everything that follows in the present tense, in response to what I type, while accurately following the established lore of Baldurs Gate, Tales of the Sword Coast, Baldurs Gate 2: Shadows of Amn, Throne of Bhaal, and Baldurs Gate 3, written in the descriptive style of R.A Salvatore. Provide names for characters, locations, groups and organizations, events, and magical objects. Characters should always use dialogue, enclosed in quotation marks when interacting with me or my party members, written in the conversational style of R.A Salvatore. Do not type, compose, dictate, influence, script, generate, control, or describe what me or my party members are doing, saying, acting, behaving, thinking, feeling, experiencing, or any other aspect concerning me or my party members throughout the entire adventure, scenario, story, location, quest, mission, scene, event, description, dialogue, and conversation. Use only one paragraph consisting of less than 80 words for all replies, descriptions, conversations, dialogue and text.")))
:general
(chris/leader-keys
:states '(normal visual)
:keymaps 'override
"la" 'gptel-send
"lm" 'gptel-menu))
#+end_src
*** Ellama
Idk, let's try this i guess
#+begin_src emacs-lisp
(use-package ellama
:init
(require 'llm-ollama)
(setopt ellama-buffer-mode 'markdown-mode
ellama-user-nick "Chris"
ellama-assistant-nick "Jeeves"
ellama-auto-scroll t
ellama-provider
(make-llm-ollama
:scheme "https"
:host "ai.tfcconnection.org"
:port 443
:chat-model "dolphin-mistral" :embedding-model "dolphin-mistral"))
(setopt ellama-providers
'(("mistral" . (make-llm-ollama
:scheme "https"
:host "ai.tfcconnection.org"
:port 443
:chat-model "mistral"
:embedding-model "mistral"))
("llama3" . (make-llm-ollama
:scheme "https"
:host "ai.tfcconnection.org"
:port 443
:chat-model "llama3.1"
:embedding-model "llama3.1"))
("openhermes" . (make-llm-ollama
:scheme "https"
:host "ai.tfcconnection.org"
:port 443
:chat-model "openhermes:7b-mistral-v2.5-q6_K"
:embedding-model "openhermes:7b-mistral-v2.5-q6_K"))
("dolphin" . (make-llm-ollama
:scheme "https"
:host "ai.tfcconnection.org"
:port 443
:chat-model "dolphin-uncensored"
:embedding-model "dolphin-uncensored"))))
:config
(defun chris/ellama-new-session (prompt)
(interactive "sAsk ellama: ")
(ellama-provider-select)
(ellama-new-session ellama-provider prompt))
:general
(chris/leader-keys
:states '(normal visual)
:keymaps 'override
"l" '(:ignore t :which-key "llm")
"la" 'ellama-chat
"lb" 'ellama-ask-about
"lc" 'ellama-code-review
"lec" 'ellama-code-improve
"leg" 'ellama-improve-grammar
"leC" 'ellama-improve-conciseness
"lew" 'ellama-improve-wording
"ls" 'ellama-provider-select))
#+end_src
** Jinx
Jinx is an enchanted spell checker for emacs. I think I'll just turn it on globally for a while and see how I feel.
#+begin_src emacs-lisp
(use-package jinx
;; :hook (emacs-startup . global-jinx-mode)
:init (flyspell-mode -1)
:config (flyspell-mode -1)
:bind (("M-c" . jinx-correct)
("C-M-$" . Jinx-Languages))
:general
(general-def jinx-mode-map
"C-S-f" 'jinx-correct))
#+end_src
** Emoji
In order to render color emojis I'll use =unicode-fonts=.
#+begin_src emacs-lisp :tangle no
(use-package unicode-fonts
:ensure t
:config
(unicode-fonts-setup))
#+end_src
Or maybe I'll use =emojify=.
#+begin_src emacs-lisp
(use-package emojify
:ensure t
:hook (after-init . global-emojify-mode)
:config
(setq emojify-display-style 'image)
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"ie" '(emojify-insert-emoji :which-key "insert emoji")))
#+end_src
** Undo-Tree
I no longer use this since I primarily use Emacs 28 across my machines and have used Emacs's built in undo-redo.
#+begin_src emacs-lisp :tangle no
(use-package undo-tree
:after evil
:config
(global-undo-tree-mode +1)
(setq evil-undo-system 'undo-tree)
:general
(general-def 'normal undo-tree-visualizer-mode-map
"j" 'undo-tree-visualize-redo
"k" 'undo-tree-visualize-undo))
#+end_src
** Undo-Fu
The same applies here as did =undo-tree= I no longer use this since I primarily use Emacs 28 across my machines and have used Emacs's built in undo-redo.
#+begin_src emacs-lisp :tangle no
(use-package undo-fu
:after evil
:config
(setq evil-undo-system 'undo-fu))
#+end_src
** Better UI
*** Olivetti
#+begin_src emacs-lisp :tangle no
(use-package olivetti
:config
(setq olivetti-body-width 0.6
olivetti-minimum-body-width 100))
#+end_src
*** Revert all buffers
#+BEGIN_SRC emacs-lisp
(global-auto-revert-mode 1)
(setq global-auto-revert-non-file-buffers t)
#+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
*** Lin and Pulsar
These two packages created by Prot are interesting to me and may help to make sure I do not loose my place in emacs so much.
#+begin_src emacs-lisp :tangle no
(use-package lin
:config
(setq lin-mode-hooks
'(bongo-mode-hook
dired-mode-hook
elfeed-search-mode-hook
git-rebase-mode-hook
ibuffer-mode-hook
ilist-mode-hook
ledger-report-mode-hook
log-view-mode-hook
magit-log-mode-hook
mu4e-headers-mode
notmuch-search-mode-hook
notmuch-tree-mode-hook
occur-mode-hook
org-agenda-mode-hook
tabulated-list-mode-hook)))
#+end_src
#+begin_src emacs-lisp
(use-package pulsar
:config
(setq pulsar-pulse-functions
;; NOTE 2022-04-09: The commented out functions are from before
;; the introduction of `pulsar-pulse-on-window-change'. Try that
;; instead.
'(recenter-top-bottom
move-to-window-line-top-bottom
reposition-window
;; bookmark-jump
;; other-window
;; delete-window
;; delete-other-windows
forward-page
backward-page
scroll-up-command
scroll-down-command
;; windmove-right
;; windmove-left
;; windmove-up
;; windmove-down
;; windmove-swap-states-right
;; windmove-swap-states-left
;; windmove-swap-states-up
;; windmove-swap-states-down
;; tab-new
;; tab-close
;; tab-next
org-next-visible-heading
org-previous-visible-heading
org-forward-heading-same-level
org-backward-heading-same-level
outline-backward-same-level
outline-forward-same-level
outline-next-visible-heading
outline-previous-visible-heading
outline-up-heading))
(setq pulsar-pulse-on-window-change t)
(setq pulsar-pulse t)
(setq pulsar-delay 0.055)
(setq pulsar-iterations 10)
(setq pulsar-face 'ffap)
(setq pulsar-highlight-face 'pulsar-yellow)
(pulsar-global-mode 1)
;; integration with the `consult' package:
(add-hook 'consult-after-jump-hook #'pulsar-recenter-top)
(add-hook 'consult-after-jump-hook #'pulsar-reveal-entry)
;; integration with the built-in `imenu':
(add-hook 'imenu-after-jump-hook #'pulsar-recenter-top)
(add-hook 'imenu-after-jump-hook #'pulsar-reveal-entry))
#+end_src
*** posframe
I'm going to find all kinds of uses for posframe
#+begin_src emacs-lisp
(use-package posframe)
#+end_src
#+begin_src emacs-lisp
(use-package vertico-posframe
:after vertico
:config
(setq vertico-posframe-min-height 10))
#+end_src
** EWW
:PROPERTIES:
:ID: 20240526T142839.041746
:END:
Builtin webbrowser for emacs. Trying it out as a text only browser for things.
#+begin_src emacs-lisp
(setq home-directory "~/")
(defun chris/eww-empv ()
"Launch the url in mpv"
(interactive)
(let ((pt (avy-with ace-link-eww
(avy-process
(mapcar #'cdr (ace-link--eww-collect))
(avy--style-fn avy-style)))))
(when (number-or-marker-p pt)
(goto-char pt)
(let ((url (get-text-property (point) 'shr-url)))
(empv-play-or-enqueue (string-replace "inv.cochrun.xyz" "youtube.com" url))))))
(defun chris/search-yt ()
"Search youtube in my own invidious instance
and show the results in eww."
(interactive)
(let* ((search (completing-read "YT Search: " nil))
(url (url-encode-url (concat "https://inv.cochrun.xyz/search?q=" search))))
(eww url)))
;; (defun chris/)
(defun chris/eww-video-dl ()
"Download the video at the url"
(interactive)
(let ((pt (avy-with ace-link-eww
(avy-process
(mapcar #'cdr (ace-link--eww-collect))
(avy--style-fn avy-style)))))
(when (number-or-marker-p pt)
(goto-char pt)
(let ((url (get-text-property (point) 'shr-url)))
(if (start-process
"yt-dlp"
"yt-dlp-output"
"yt-dlp" "-o" (concat home-directory "Videos/%(title)s.%(ext)s") url)
(message (concat "downloading => " url))
(message "idk this failed I guess..."))
(pop-to-buffer "yt-dlp-output")
(comint-mode)
(evil-normal-state)
(general-def 'normal comint-mode-map
"q" 'kill-buffer-and-window)))))
(setq eww-search-prefix "https://search.tfcconnection.org/search?q=")
(general-def 'normal eww-mode-map
"gv" 'chris/eww-empv
"gV" 'chris/eww-video-dl)
(chris/leader-keys
:state 'normal
:keymap 'eww-mode-map
"fs" '(eww-browse :which-key "browse"))
(chris/leader-keys
:state 'normal
"sY" '(chris/search-yt :which-key "search youtube"))
#+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 :tangle no
(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 :tangle no
(use-package prescient
:config
(prescient-persist-mode +1)
:after selectrum)
#+end_src
#+BEGIN_SRC emacs-lisp :tangle no
(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
*** VERTICO
Vertico is an alternative to Selectrum. Maybe using it will give me an even better experience over selectrum.
#+BEGIN_SRC emacs-lisp
(use-package vertico
:init
(vertico-mode)
;; Different scroll margin
;; (setq vertico-scroll-margin 0)
;; Show more candidates
(setq vertico-count 10)
;; Grow and shrink the Vertico minibuffer
(setq vertico-resize t)
;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
(setq vertico-cycle t)
:general
(general-def 'vertico-map
"C-j" 'vertico-next
"C-k" 'vertico-previous)
)
;; A few more useful configurations...
(use-package emacs
:init
;; Add prompt indicator to `completing-read-multiple'.
;; Alternatively try `consult-completing-read-multiple'.
(defun crm-indicator (args)
(cons (concat "[CRM] " (car args)) (cdr args)))
(advice-add #'completing-read-multiple :filter-args #'crm-indicator)
;; Do not allow the cursor in the minibuffer prompt
(setq minibuffer-prompt-properties
'(read-only t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
;; Emacs 28: 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)
;; Enable recursive minibuffers
(setq enable-recursive-minibuffers t))
#+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 vertico
:config
(setq consult-narrow-key "<"
consult-project-root-function 'projectile-project-root)
(consult-customize
consult-org-heading :preview-key nil)
(defun chris/consult-ripgrep-for-stepdata ()
"Make ripgrep search hidden files for stepdata"
(interactive)
(let ((consult-ripgrep-args
(concat "rg "
"--null "
"--line-buffered "
"--color=never "
"--max-columns=1000 "
"--path-separator / "
"--no-heading "
"--line-number "
;; adding these to default
"--smart-case "
"--hidden "
;; defaults
"."
)))
(consult-ripgrep)))
(setq consult-imenu-config
'((emacs-lisp-mode :toplevel "Functions" :types
((102 "Functions" font-lock-function-name-face)
(109 "Macros" font-lock-function-name-face)
(112 "Packages" font-lock-constant-face)
(116 "Types" font-lock-type-face)
(118 "Variables" font-lock-variable-name-face)))))
(setq xref-show-xrefs-function 'consult-xref)
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"si" 'consult-imenu
"so" 'consult-org-heading
"sf" 'consult-find
"sm" 'bookmark-jump
"sf" 'consult-flymake
"sx" 'xref-show-xrefs
"sy" 'consult-yank-from-kill-ring
"sb" 'consult-eglot-symbols))
#+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 vertico
:config
(setq marginalia--cache-size 60000))
#+end_src
Along with Marginalia, let's add in icons.
#+begin_src emacs-lisp
(use-package all-the-icons-completion
:after vertico
:config
(all-the-icons-completion-mode))
#+end_src
*** Embark
Embark or do something.
#+BEGIN_SRC emacs-lisp
(use-package embark
:ensure t
:general
('vertico-map
"C-'" 'embark-act)
('normal 'org-mode-map
"C-'" 'embark-act)
:config
(defun embark-which-key-indicator ()
"An embark indicator that displays keymaps using which-key.
The which-key help message will show the type and value of the
current target followed by an ellipsis if there are further
targets."
(lambda (&optional keymap targets prefix)
(if (null keymap)
(which-key--hide-popup-ignore-command)
(which-key--show-keymap
(if (eq (plist-get (car targets) :type) 'embark-become)
"Become"
(format "Act on %s '%s'%s"
(plist-get (car targets) :type)
(embark--truncate-target (plist-get (car targets) :target))
(if (cdr targets) "" "")))
(if prefix
(pcase (lookup-key keymap prefix 'accept-default)
((and (pred keymapp) km) km)
(_ (key-binding prefix 'accept-default)))
keymap)
nil nil t (lambda (binding)
(not (string-suffix-p "-argument" (cdr binding))))))))
(setq embark-indicators
'(embark-which-key-indicator
embark-highlight-indicator
embark-isearch-highlight-indicator))
(defun embark-hide-which-key-indicator (fn &rest args)
"Hide the which-key indicator immediately when using the completing-read prompter."
(which-key--hide-popup-ignore-command)
(let ((embark-indicators
(remq #'embark-which-key-indicator embark-indicators)))
(apply fn args)))
(advice-add #'embark-completing-read-prompter
:around #'embark-hide-which-key-indicator)
)
(use-package embark-consult)
#+END_SRC
*** WGREP
#+begin_src emacs-lisp
(use-package wgrep)
#+end_src
*** Company
#+begin_src emacs-lisp :tangle no
(use-package company
:config
(global-company-mode +1)
:custom
(company-dabbrev-other-buffers t)
(company-minimum-prefix-length 1)
(company-idle-delay 0.1)
:general
(general-def '(normal insert) company-active-map
"TAB" 'company-complete-selection
"RET" 'company-complete-selection)
(general-def '(normal insert) lsp-mode-map
"TAB" 'company-indent-or-complete-common))
;; (use-package company-box
;; :hook (company-mode . company-box-mode))
#+end_src
#+begin_src emacs-lisp :tangle no
(use-package company-dict
:after company)
#+end_src
*** Corfu
:PROPERTIES:
:ID: 20240104T104041.544024
:END:
Trying out corfu instead of company
#+BEGIN_SRC emacs-lisp
(use-package corfu
:ensure t
;; Optional customizations
:custom
(corfu-cycle t) ;; Enable cycling for `corfu-next/previous'
(corfu-auto t) ;; Enable auto completion
(corfu-separator ?\s) ;; Orderless field separator
(corfu-quit-no-match nil) ;; Never quit, even if there is no match
(corfu-preview-current nil) ;; Enable current candidate preview
(corfu-preselect-first nil) ;; Enable candidate preselection
(corfu-on-exact-match 'insert) ;; Configure handling of exact matches
(corfu-echo-documentation '(1.0 . 0.2)) ;; Disable documentation in the echo area
(corfu-scroll-margin 5) ;; Use scroll margin
(corfu-count 10)
(corfu-auto-prefix 2)
(corfu-auto-delay 0.5)
;; You may want to enable Corfu only for certain modes.
;; :hook ((prog-mode . corfu-mode)
;; (shell-mode . corfu-mode)
;; (eshell-mode . corfu-mode))
;; Recommended: Enable Corfu globally.
;; This is recommended since dabbrev can be used globally (M-/).
:init
(add-hook 'global-corfu-mode-hook #'evil-collection-corfu-setup)
(global-corfu-mode)
(corfu-echo-mode)
(advice-remove 'corfu--setup 'evil-normalize-keymaps)
(advice-remove 'corfu--teardown 'evil-normalize-keymaps)
(advice-add 'corfu--setup :after (lambda (&rest r) (evil-normalize-keymaps)))
(advice-add 'corfu--teardown :after (lambda (&rest r) (evil-normalize-keymaps)))
:general
('corfu-map
"C-j" 'corfu-next
"C-k" 'corfu-previous
"M-SPC" 'corfu-insert-separator))
;; Optionally use the `orderless' completion style. See `+orderless-dispatch'
;; in the Consult wiki for an advanced Orderless style dispatcher.
;; Enable `partial-completion' for files to allow path expansion.
;; You may prefer to use `initials' instead of `partial-completion'.
(use-package orderless
:ensure t
:init
;; Configure a custom style dispatcher (see the Consult wiki)
;; (setq orderless-style-dispatchers '(+orderless-dispatch)
;; orderless-component-separator #'orderless-escapable-split-on-space)
(setq completion-styles '(orderless)
completion-category-defaults nil
completion-category-overrides '((file (styles . (partial-completion)))))
(defun flex-if-twiddle (pattern _index _total)
(when (string-prefix-p "~" pattern)
`(orderless-flex . ,(substring pattern 1))))
(defun first-initialism (pattern index _total)
(if (= index 0) 'orderless-initialism))
(defun without-if-bang (pattern _index _total)
(cond
((equal "!" pattern)
'(orderless-literal . ""))
((string-prefix-p "!" pattern)
`(orderless-without-literal . ,(substring pattern 1)))))
(setq orderless-matching-styles '(orderless-literal orderless-regexp)
orderless-style-dispatchers '(flex-if-twiddle
without-if-bang))
)
;; Use dabbrev with Corfu!
(use-package dabbrev
:after corfu
;; Swap M-/ and C-M-/
:bind (("M-/" . dabbrev-completion)
("C-M-/" . dabbrev-expand)))
(use-package cape
:ensure t
;; Bind dedicated completion commands
:bind (("C-c p p" . completion-at-point) ;; capf
("C-c p t" . complete-tag) ;; etags
("C-c p d" . cape-dabbrev) ;; or dabbrev-completion
("C-c p f" . cape-file)
("C-c p k" . cape-keyword)
("C-c p s" . cape-symbol)
("C-c p a" . cape-abbrev)
;; ("C-c p i" . cape-ispell)
("C-c p l" . cape-line)
("C-c p w" . cape-dict)
("C-c p \\" . cape-tex)
("C-c p _" . cape-tex)
("C-c p ^" . cape-tex)
("C-c p &" . cape-sgml)
("C-c p r" . cape-rfc1345))
:init
;; Add `completion-at-point-functions', used by `completion-at-point'.
(add-to-list 'completion-at-point-functions #'cape-file)
;; (add-to-list 'completion-at-point-functions #'cape-tex)
(add-to-list 'completion-at-point-functions #'cape-keyword)
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
;; (add-to-list 'completion-at-point-functions #'cape-sgml)
;; (defvar completion-at-point-functions '(cape-dabbrev cape-keyword cape-file tempel-complete
;; pcomplete-completions-at-point
;; ispell-completion-at-point))
;; (add-to-list 'completion-at-point-functions #'cape-rfc1345)
;; (add-to-list 'completion-at-point-functions #'cape-abbrev)
;; (add-to-list 'completion-at-point-functions #'cape-ispell)
;; (add-to-list 'completion-at-point-functions #'cape-dict)
;; (add-to-list 'completion-at-point-functions #'cape-symbol)
;; (add-to-list 'completion-at-point-functions #'cape-line)
;; (remove #'cape-ispell completion-at-point-functions)
;; (remove #'cape-symbol completion-at-point-functions)
:config
(setq cape-dabbrev-min-length 4)
)
#+END_SRC
*** kind-icon
Kind icon adds icons to corfu
#+begin_src emacs-lisp
(use-package kind-icon
:ensure t
:after corfu
:custom
(kind-icon-default-face 'corfu-default) ; to compute blended backgrounds correctly
:config
(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))
#+end_src
** Devdocs
Devdocs.io is a pretty great place to see documentation on nearly any developer technology. Devdocs.el will bring all that documentation right inside Emacs.
#+begin_src emacs-lisp
(use-package devdocs
:config
(evil-collection-devdocs-setup)
:general
(chris/leader-keys 'normal
"hd" 'devdocs-lookup))
#+end_src
** YASnippet
YASnippet is a templating system. It's powerful.
#+begin_src emacs-lisp :tangle no
(use-package yasnippet
:config
(setq yas-snippet-dirs (list (expand-file-name "yasnippets/" user-emacs-directory)))
(yas-global-mode 1))
#+end_src
** Tempel
Tempel is another templating system. Also perhaps even more powerful with it's elisp way of creating snippets, but elisp is tougher to work around. But, I'll give it a try.
#+BEGIN_SRC emacs-lisp
(use-package tempel
:bind (("M-+" . tempel-complete) ;; Alternative tempel-expand
("M-'" . tempel-insert)
("C-M-<return>" . tempel-done))
:init
;; Setup completion at point
(defun tempel-setup-capf ()
;; Add the Tempel Capf to `completion-at-point-functions'. `tempel-expand'
;; only triggers on exact matches. Alternatively use `tempel-complete' if
;; you want to see all matches, but then Tempel will probably trigger too
;; often when you don't expect it.
;; NOTE: We add `tempel-expand' *before* the main programming mode Capf,
;; such that it will be tried first.
(setq-local completion-at-point-functions
(cons #'tempel-complete
completion-at-point-functions)))
(add-hook 'conf-mode-hook 'tempel-setup-capf)
(add-hook 'prog-mode-hook 'tempel-setup-capf)
(add-hook 'text-mode-hook 'tempel-setup-capf)
(add-hook 'org-mode-hook 'tempel-setup-capf)
(setq tempel-path "/home/chris/.emacs.d/templates")
;; Optionally make the Tempel templates available to Abbrev,
;; either locally or globally. `expand-abbrev' is bound to C-x '.
;; (add-hook 'prog-mode-hook #'tempel-abbrev-mode)
;; (tempel-global-abbrev-mode)
:general
(chris/leader-keys
"ic" 'tempel-insert)
(general-def 'insert tempel-map
"C-l" 'tempel-next
"C-h" 'tempel-previous))
(use-package tempel-collection)
#+END_SRC
** Projectile
I'm going to use projectile to keep my projects inline.
#+begin_src emacs-lisp
(use-package projectile
:ensure t
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"op" 'projectile-switch-open-project
"gc" 'projectile-compile-project
"gr" 'projectile-run-project
"gd" 'projectile-run-gdb
"fp" 'project-find-file
"fP" 'project-switch-project))
#+end_src
** Project
Here are project specific commands
#+begin_src emacs-lisp
(chris/leader-keys
:states 'normal
:keymaps 'override
"p!" 'project-shell-command
"p&" 'project-async-shell-command
"pD" 'project-dired
"pF" 'project-or-external-find-file
"pG" 'project-or-external-find-regexp
"pb" 'project-switch-to-buffer
"pc" 'project-compile
"pd" 'project-find-dir
"pe" 'project-eshell
"pf" 'project-find-file
"pg" 'project-find-regexp
"pk" 'project-kill-buffers
"pp" 'project-switch-project
"pr" 'project-query-replace-regexp
"ps" 'project-shell
"pv" 'project-vc-dir
"px" 'project-execute-extended-command)
(general-def 'normal
"p" 'evil-paste-after)
#+end_src
** Gitlab
Because most of my projects are hosted on gitlab right now, I think having a client access to it from emacs seems like a really good idea.
#+begin_src emacs-lisp
(use-package lab
:config
(defun chris/gitlab-token ()
(interactive)
(string-clean-whitespace (shell-command-to-string "rbw get 'gitlab token'")))
(setq lab-host "https://gitlab.com"
lab-token (chris/gitlab-token)))
#+end_src
** HTTPD
In order to view created websites, I'll use =simple-httpd= to get a web server running in emacs for preview.
#+BEGIN_SRC emacs-lisp
(use-package simple-httpd
:ensure 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)
(general-def 'normal
"gl" 'avy-goto-line))
#+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
:general
(general-def 'normal
"gL" 'ace-link))
#+end_src
** Window Management
#+begin_src emacs-lisp
(setq display-buffer-alist
(if (string= system-name "syl")
'(("\\*e?shell\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.25))
("\\*e?shell\\*<20>"
(display-buffer-same-window))
("\\*e?shell-terminal\\*"
(display-buffer-same-window))
("*helpful*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.4))
("*compilation*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.4))
("*rustic-compilation*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.4))
("*gud-presenter*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.4))
("*dired-side*"
(display-buffer-in-side-window)
(side . left)
(window-width . 0.3))
("*org-roam*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.4))
("\\*Agenda Commands\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.30))
("\\*elfeed-entry\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.60))
("*Agenda Commands*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.30))
("*Ledger Report*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.40))
("\\*Bongo-Elfeed Queue\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.25))
("\\*Async Shell Command\\*"
(display-buffer-no-window))
)
'(("\\*e?shell\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.25))
("\\*e?shell\\*"
(display-buffer-same-window))
("\\*e?shell-terminal\\*"
(display-buffer-same-window))
("*helpful*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.4))
("*compilation*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.25))
("*rustic-compilation*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.25))
("\\*Agenda Commands\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.30))
("*gud-presenter*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.25))
("*org-roam*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.3))
("\\*elfeed-entry\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.70))
("*Agenda Commands*"
(display-buffer-in-side-window)
(side . right)
(window-width . 0.30))
("\\*Bongo-Elfeed Queue\\*"
(display-buffer-in-side-window)
(side . bottom)
(window-height . 0.25))
("\\*Async Shell Command\\*"
(display-buffer-no-window))
)
))
#+end_src
Since I like to make my window manager handle a lot of the window management, I will create a helper function for closing windows.
#+begin_src emacs-lisp
(defun chris/kill-buffer-frame ()
"Kills the active buffer and frame"
(interactive)
(kill-current-buffer)
(delete-frame))
#+end_src
*** Ace Window
#+begin_src emacs-lisp
(use-package ace-window
:config (ace-window-display-mode)
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"ww" '(ace-window :which-key "select window")))
#+end_src
** Help
#+begin_src emacs-lisp
(use-package helpful
:ensure nil
:commands (helpful-callable helpful-variable helpful-command helpful-key)
:general
(general-def 'normal 'helpful-mode-map
"q" 'helpful-kill-buffers)
:config
(defun helpful--autoloaded-p (sym buf)
"Return non-nil if function SYM is autoloaded."
(-when-let (file-name (buffer-file-name buf))
(setq file-name (s-chop-suffix ".gz" file-name))
(condition-case nil
(help-fns--autoloaded-p sym file-name)
; new in Emacs 29.0.50
; see https://github.com/Wilfred/helpful/pull/283
(error (help-fns--autoloaded-p sym)))))
(defun helpful--skip-advice (docstring)
"Remove mentions of advice from DOCSTRING."
(let* ((lines (s-lines docstring))
(relevant-lines
(--take-while
(not (or (s-starts-with-p ":around advice:" it)
(s-starts-with-p "This function has :around advice:" it)))
lines)))
(s-trim (s-join "\n" relevant-lines)))))
#+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 t)
#+end_src
** Languages
Before getting into languages, often times compilation of things use ansi-colors, so in order to make sure those characters get rendered properly, here we make sure that those colors work ok.
#+BEGIN_SRC emacs-lisp
(defvar read-symbol-positions-list nil)
(add-to-list 'comint-output-filter-functions 'ansi-color-process-output)
(add-hook 'comint-mode-hook 'ansi-color-for-comint-mode-on)
(add-hook 'compilation-filter-hook 'ansi-color-for-comint-mode-on)
(setq compilation-environment '("TERM=xterm-256color")
compilation-scroll-output t)
(defun my/advice-compilation-filter (f proc string)
(funcall f proc (xterm-color-filter string)))
(advice-add 'compilation-filter :around #'my/advice-compilation-filter)
#+END_SRC
Let's also set =hl-line-mode= to be on for comint and prog modes
#+BEGIN_SRC emacs-lisp
(add-hook 'comint-mode-hook 'hl-line-mode)
(add-hook 'prog-mode-hook 'hl-line-mode)
(add-hook 'prog-mode-hook 'hs-minor-mode)
#+END_SRC
*** Lisp
Also here are some lisp specific stuff
#+begin_src emacs-lisp
(use-package smartparens
:config
(smartparens-global-mode +1)
:general
('normal emacs-lisp-mode-map
"gl" 'sp-forward-slurp-sexp
"gh" 'sp-backward-slurp-sexp
"C-l" 'sp-forward-sexp
"C-h" 'sp-backward-sexp)
('normal lisp-mode-map
"gl" 'sp-forward-slurp-sexp
"gh" 'sp-backward-slurp-sexp
"C-l" 'sp-forward-sexp
"C-h" 'sp-backward-sexp)
('normal lisp-shared-mode-map
"gl" 'sp-forward-slurp-sexp
"gh" 'sp-backward-slurp-sexp
"C-l" 'sp-forward-sexp
"C-h" 'sp-backward-sexp)
('normal sly-mrepl-mode-map
"gl" 'sp-forward-slurp-sexp
"gh" 'sp-backward-slurp-sexp
"C-l" 'sp-forward-sexp
"C-h" 'sp-backward-sexp)
('normal scheme-mode-map
"gl" 'sp-forward-slurp-sexp
"gh" 'sp-backward-slurp-sexp
"C-l" 'sp-forward-sexp
"C-h" 'sp-backward-sexp)
('normal cider-repl-mode-map
"gl" 'sp-forward-slurp-sexp
"gh" 'sp-backward-slurp-sexp
"C-l" 'sp-forward-sexp
"C-h" 'sp-backward-sexp)
('normal clojurescript-mode-map
"gl" 'sp-forward-slurp-sexp
"gh" 'sp-backward-slurp-sexp
"C-l" 'sp-forward-sexp
"C-h" 'sp-backward-sexp))
#+end_src
#+begin_src emacs-lisp
(use-package paredit
:hook ((lisp-mode . enable-paredit-mode)
(emacs-lisp-mode . enable-paredit-mode)
(scheme-mode . enable-paredit-mode))
:general
('normal lisp-mode-map
"gl" 'paredit-forward-slurp-sexp
"gh" 'paredit-backward-slurp-sexp
"C-l" 'paredit-forward
"C-h" 'paredit-backward)
('normal emacs-lisp-mode-map
"gl" 'paredit-forward-slurp-sexp
"gh" 'paredit-backward-slurp-sexp
"C-l" 'paredit-forward
"C-h" 'paredit-backward)
('normal lisp-shared-mode-map
"gl" 'paredit-forward-slurp-sexp
"gh" 'paredit-backward-slurp-sexp
"C-l" 'paredit-forward
"C-h" 'paredit-backward)
('normal sly-mrepl-mode-map
"gl" 'paredit-forward-slurp-sexp
"gh" 'paredit-backward-slurp-sexp
"C-l" 'paredit-forward
"C-h" 'paredit-backward)
('normal scheme-mode-map
"gl" 'paredit-forward-slurp-sexp
"gh" 'paredit-backward-slurp-sexp
"C-l" 'paredit-forward
"C-h" 'paredit-backward)
('normal geiser-mode-map
"gl" 'paredit-forward-slurp-sexp
"gh" 'paredit-backward-slurp-sexp
"C-l" 'paredit-forward
"C-h" 'paredit-backward)
('normal cider-repl-mode-map
"gl" 'paredit-forward-slurp-sexp
"gh" 'paredit-backward-slurp-sexp
"C-l" 'paredit-forward
"C-h" 'paredit-backward))
;; (remove-hook 'prog-mode-hook 'enable-paredit-mode)
(load (if (string= system-name "kaladin")
"/home/chris/quicklisp/clhs-use-local.el"
"/home/chris/.local/share/quicklisp/clhs-use-local.el") t)
#+end_src
Finally, here are some auto modes I'd like to setup
#+begin_src emacs-lisp
(add-to-list 'auto-mode-alist '("\\.yuck?\\'" . lisp-data-mode))
#+end_src
**** Sly Common Lisp
Using sly makes a lot better common-lisp interaction within emacs.
#+begin_src emacs-lisp
(use-package sly
:mode
("\\.lisp\\'" . sly-mode)
("\\.lisp\\'" . lisp-mode)
:config
(defun chris/start-nyxt-repl ()
"Start the repl and sly connection for nyxt"
(interactive)
(sly-connect "localhost" 4006))
(setq sly-connection-poll-interval 0.1)
(defun sly-critique-file ()
"Lint this file with lisp-critic"
(interactive)
(sly-eval-async '(ql:quickload :lisp-critic))
(sly-eval-async
`(lisp-critic:critique-file ,(buffer-file-name))))
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"os" 'sly
"gc" 'compile)
(general-def 'normal lisp-mode-shared-map
"gcr" 'sly
"ge" 'sly-eval-defun
"gE" 'sly-eval-last-expression
"gp" 'sly-pprint-eval-last-expression))
#+end_src
**** Scheme Geiser
#+begin_src emacs-lisp
;; (general-def 'normal scheme-mode-map
;; "gl" 'paredit-)
#+end_src
#+begin_src emacs-lisp
(use-package geiser
:config
:general
(general-def 'normal geiser-mode-map
"gcr" 'geiser
"ge" 'geiser-eval-definition-and-go
"gE" 'geiser-eval-last-sexp
"gp" 'geiser-eval-last-sexp-and-print
"gl" 'paredit-forward-slurp-sexp))
#+end_src
**** Emacs Lisp
#+begin_src emacs-lisp
(general-def 'normal emacs-lisp-mode-map
"ge" 'eval-defun
"gE" 'eval-last-sexp
"gp" 'pp-eval-last-sexp)
#+end_src
*** Tree Sitter
I'm gonna try this to speed up emacs and make it nicer
#+begin_src emacs-lisp :tangle no
(use-package tree-sitter
:config
(global-tree-sitter-mode +1)
)
(use-package tree-sitter-langs)
#+end_src
#+begin_src emacs-lisp
#+end_src
*** C++
In c++ I just want to make sure lsp is called when enter c++-mode
#+BEGIN_SRC emacs-lisp
(defun astyle-this-buffer ()
"Use astyle command to auto format c/c++ code."
(interactive "r")
(let* ((original-point (point))) ;; save original point before processing, thanks to @Scony
(progn
(if (executable-find "astyle")
(shell-command-on-region
(point-min) (point-max)
(concat
"astyle"
" --style=" cc-mode-code-style
" --indent=spaces=" (number-to-string c-basic-offset)
" --pad-oper"
" --pad-header"
" --break-blocks"
" --delete-empty-lines"
" --align-pointer=type"
" --align-reference=name")
(current-buffer) t
(get-buffer-create "*Astyle Errors*") t)
(message "Cannot find binary \"astyle\", please install first."))
(goto-char original-point)))) ;; restore original point
(defun astyle-before-save ()
"Auto styling before saving."
(interactive)
(when (member major-mode '(cc-mode c++-mode c-mode))
(astyle-this-buffer)))
(add-hook 'c-mode-common-hook (lambda () (add-hook 'before-save-hook 'astyle-before-save)))
#+END_SRC
*** Rust
:PROPERTIES:
:ID: 20231120T054703.266056
:END:
I'd like to start learning and using rust if I can.
#+begin_src emacs-lisp
(use-package rustic
:config
;; comment to disable rustfmt on save
(setq rustic-format-on-save t
rustic-lsp-client 'eglot
rustic-clippy-arguments "-- -W clippy::pedantic -W clippy::nursery -W clippy::unwrap_used")
(advice-add 'eglot-completion-at-point :around #'cape-wrap-buster)
(add-to-list 'compilation-error-regexp-alist rustic-compilation-error)
(add-to-list 'compilation-error-regexp-alist rustic-compilation-warning)
:general
(general-def 'normal rustic-mode-map
"!" 'rustic-run-shell-command
"gC" 'rustic-cargo-clippy
"gA" 'rustic-cargo-add
"gt" 'rustic-cargo-test
"gT" 'rustic-cargo-current-test)
(chris/leader-keys 'normal rustic-mode-map
"gc" 'rustic-compile
"gr" 'projectile-run-project
"si" 'consult-imenu-multi
"ga" 'rustic-cargo-add
"gC" 'rustic-cargo-clippy
"gt" 'rustic-cargo-test))
#+end_src
#+begin_src emacs-lisp
(use-package slint-mode
:mode "\\.slint\\'"
:general
(general-def 'normal slint-mode-map
"!" 'rustic-run-shell-command
"gC" 'rustic-cargo-clippy
"gA" 'rustic-cargo-add
"gt" 'rustic-cargo-test
"gT" 'rustic-cargo-current-test)
(chris/leader-keys 'normal slint-mode-map
"gc" 'rustic-compile
"gr" 'projectile-run-project
"si" 'consult-imenu-multi
"ga" 'rustic-cargo-add
"gC" 'rustic-cargo-clippy
"gt" 'rustic-cargo-test))
#+end_src
*** Web
For developing websites, I like to use web-mode
#+begin_src emacs-lisp
(defun chris/web-mode-setup ()
"some setup for web development"
(setq-local completion-at-point-functions
(list #'cape-dabbrev #'cape-keyword #'tempel-complete)))
(use-package web-mode
:mode "\\.html\\'"
:config
(add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.phtml?\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.tpl\\.php\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.djhtml?\\'" . web-mode))
(setq web-mode-enable-auto-pairing t
web-mode-enable-auto-expanding t
web-mode-enable-auto-closing t
web-mode-enable-current-column-highlight t
web-mode-enable-current-element-highlight t)
(add-to-list 'web-mode-hook #'chris/web-mode-setup)
:general
(general-def 'normal web-mode-map
"TAB" 'indent-according-to-mode)
(general-def 'insert web-mode-map
"TAB" 'indent-according-to-mode))
#+end_src
*** Lua
Since I use the Awesome WM I thought it'd be good to have lua around. It's also in a lot of things.
#+begin_src emacs-lisp
(use-package lua-mode
:mode ("\\.lua\\'" . lua-mode))
#+end_src
*** Nix
I've been transitioning more of my OS to NixOS. Let's get =nix-mode= working.
#+begin_src emacs-lisp
(use-package nix-mode
:mode "\\.nix\\'"
:general
(chris/leader-keys 'normal 'override
"xf" 'nix-flake))
#+end_src
*** LSP
LSP is useful...
#+begin_src emacs-lisp :tangle no
(use-package lsp-mode
:commands (lsp lsp-deferred)
:custom
(lsp-completion-provider :none)
:hook
(c++-mode . lsp-deferred)
(lsp-completion-mode . my/lsp-mode-setup-completion)
:init
(setq lsp-keymap-prefix "C-c l")
(defun my/lsp-mode-setup-completion ()
(setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
'(flex)))
:config
(setq lsp-lens-enable t
lsp-signature-auto-activate nil
read-process-output-max (* 1024 1024)
lsp-clangd-binary-path "/usr/bin/clangd")
(lsp-enable-which-key-integration t)
(add-to-list 'lsp-language-id-configuration '(qml-mode . "qml"))
(lsp-register-client
(make-lsp-client :new-connection (lsp-stdio-connection "qml-lsp")
:activation-fn (lsp-activate-on "qml")
:server-id 'qml))
:general
(general-def 'normal c++-mode-map
"gf" 'lsp-clangd-find-other-file)
(general-def 'normal c++-ts-mode-map
"gf" 'lsp-clangd-find-other-file))
(use-package lsp-ui
:hook (lsp-mode . lsp-ui-mode)
:custom
(lsp-ui-doc-position 'at-point))
(use-package lsp-treemacs
:after lsp)
(use-package consult-lsp
:after lsp)
#+end_src
*** Eglot
Let's give eglot a try.
#+begin_src emacs-lisp
(use-package eglot
:commands eglot
:hook
(c++-mode . eglot-ensure)
(c++-ts-mode . eglot-ensure)
(rust-mode . eglot-ensure)
(rustic-mode . eglot-ensure)
(rust-ts-mode . eglot-ensure)
:config
(defun chris/eglot-capf ()
(setq-local completion-at-point-functions
(list (cape-capf-super
#'eglot-completion-at-point
#'tempel-expand
#'cape-file))))
(add-hook 'eglot-managed-mode-hook #'chris/eglot-capf)
:general
(general-def 'normal eglot-mode-map
"ga" 'eglot-code-actions
"gi" 'eglot-find-implementation
"gr" 'eglot-rename
"gR" 'xref-find-references))
#+end_src
#+begin_src emacs-lisp
(use-package consult-eglot
:general
(general-def 'normal eglot-mode-map
"gs" 'consult-eglot-symbols))
#+end_src
*** CMAKE
#+begin_src emacs-lisp
(use-package cmake-mode
:mode ("\\CMakeLists.txt\\'" . cmake-mode))
#+end_src
*** 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 :tangle no
(use-package friar
:straight (:host github :repo "warreq/friar" :branch "master"
:files (:defaults "*.lua" "*.fnl"))
:after fennel-mode)
#+end_src
*** Clojure
I'm gonnna dabble in using clojure for the website
#+begin_src emacs-lisp
;; First install the package:
(use-package flycheck-clj-kondo
:ensure t)
;; then install the checker as soon as `clojure-mode' is loaded
(use-package clojure-mode
:ensure t
:config
(require 'flycheck-clj-kondo))
(use-package cider
:after clojure-mode)
#+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
:config
(setq docker-run-as-root 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.
*** Just
:PROPERTIES:
:ID: 20240705T095129.209161
:END:
Let's use just-mode
#+begin_src emacs-lisp
(use-package just-mode
:mode ("\\.just\\'" . just-mode))
#+end_src
#+begin_src emacs-lisp
(use-package justl
:config
(defun chris/justl-exec-recipe ()
"Execute a recipe in the justfile from the minibuffer"
(interactive)
(let* ((justfile (justl--find-justfile (project-root (eglot--current-project))))
(recipes (cl-loop for recipe in (justl--get-recipes justfile)
collect (cdr (assoc 'name recipe))))
(recipe (completing-read "Which recipe to run: " recipes)))
(if (get-buffer "*just*")
(justl--exec "just1" recipe `(,recipe))
(justl--exec justl-executable recipe `(,recipe)))
(message "%s" recipe)))
:general
(general-def 'normal justl-mode-map
"e" 'justl-exec-recipe
"E" 'justl-exec-eshell
"<return>" 'justl-exec-recipe)
(chris/leader-keys
:states 'normal
:keymaps 'rustic-mode-map
"gj" 'chris/justl-exec-recipe))
#+end_src
*** 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
*** Markdown
It's probably smart to have markdown.
#+begin_src emacs-lisp
(use-package markdown-mode
:mode (("\\.md\\'" . markdown-mode)
("\\.rmd\\'". markdown-mode))
:config
(setq markdown-fontify-code-blocks-natively t)
(add-hook 'markdown-mode-hook 'chris/org-mode-setup)
(custom-set-faces '(markdown-code-face ((t (:inherit org-block)))))
:general
(general-def 'normal markdown-mode-map
"C-j" 'markdown-next-visible-heading
"M-j" 'markdown-move-down
"C-k" 'markdown-previous-visible-heading
"M-k" 'markdown-move-up
"C-<return>" 'markdown-insert-list-item)
(general-def 'insert markdown-mode-map
"C-<return>" 'markdown-insert-list-item))
#+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))
;; (use-package company-qml
;; :after qml-mode
;; :config
;; ;; (add-to-list 'company-backends 'company-qml)
;; )
;; (setq company-backends
;; '(company-bbdb company-semantic company-cmake company-capf company-clang company-files
;; (company-dabbrev-code company-gtags company-etags company-keywords)
;; company-oddmuse company-dabbrev))
;; (use-package qt-pro-mode
;; :after qml-mode)
#+end_src
*** CSV
Sometimes I need to edit CSV files quickly
#+begin_src emacs-lisp
(use-package csv-mode
:mode ("\\.csv\\'" . csv-mode))
#+end_src
*** Restclient
Sometimes dealing with REST APIs it's easiest to do this incrementally with restclient.el
#+begin_src emacs-lisp
(use-package restclient
:commands (restclient-mode))
#+end_src
Let's also add org-babel support for this to create documentation easier.
#+begin_src emacs-lisp
(use-package ob-restclient
:after org)
#+end_src
*** Dart/Flutter
I may get into flutter development over using felgo..... but i'm not happy about it....
#+begin_src emacs-lisp
(use-package dart-mode
:mode ("\\.dart\\'" . dart-mode)
:hook (dart-mode . lsp-deferred)
:general
(general-def 'normal dart-mode-map
"gr" 'flutter-run-or-hot-reload
"gR" 'lsp-dart-dap-flutter-hot-restart))
(use-package flutter
:after dart
:general
(chris/leader-keys dart-mode-map
"rf" 'flutter-run-or-hot-reload))
(use-package hover
:after dart)
#+end_src
Let's also add the android-sdk tools to emacs' path.
#+begin_src emacs-lisp
;; (add-to-list 'exec-path "/opt/android-sdk/cmdline-tools/latest/bin")
#+end_src
*** php-mode
Sometimes I have to deal with cruddy php-mode
#+begin_src emacs-lisp
(use-package php-mode
:mode ("\\.php\\'" . php-mode))
#+end_src
** direnv
#+begin_src emacs-lisp
(use-package direnv
:config
(direnv-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
:config
(defun chris/dired-open-xdg ()
"Open the file-at-point in the appropriate program"
(interactive)
(let ((file (file-truename (ignore-errors (dired-get-file-for-visit)))))
(message (concat "Opening ==> " file))
(call-process "xdg-open" nil 0 nil file)))
(defun chris/dired-open-wm ()
"Open dired as file-manager in wm"
(interactive)
(with-selected-frame (make-frame '((name . "dired")))
(dired-jump)
(toggle-truncate-lines +1)))
(defun chris/dired-open-videos ()
"Open dired in Videos as file-manager in wm"
(interactive)
(with-selected-frame (make-frame '((name . "dired")))
(dired "/home/chris/vids/")
(toggle-truncate-lines +1)))
(defun chris/setup-dired ()
"setup dired"
(setq truncate-lines t)
(visual-line-mode nil)
(dired-hide-details-mode t))
(defun chris/open-dired-sidewindow ()
"Open dired as a side window to the main window on the left"
(interactive)
(with-current-buffer (get-buffer-create "dired-side")
(dired-jump)))
(setq dired-dwim-target t
delete-by-moving-to-trash t
dired-mouse-drag-files t)
(setq dired-listing-switches "-aoh --group-directories-first")
(setq dired-hide-details-hide-symlink-targets nil
dired-kill-when-opening-new-dired-buffer t)
(add-hook 'dired-mode-hook 'chris/setup-dired)
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"od" '(dired-jump :which-key "open dired here")
"oD" '(dired :which-key "open dired select"))
(chris/leader-keys
:states 'normal
:keymaps 'override
"sF" '(fd-dired :which-key "search in directory with fd"))
('normal dired-mode-map
"q" 'kill-current-buffer
"C-<return>" 'chris/dired-open-xdg
"M-<return>" 'ffap-other-window
"h" 'dired-up-directory
"l" 'dired-find-file
"C-'" 'embark-act))
#+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)))
(defun chris/dired-yank-fullpath ()
"get the fill file path at point and put into kill-ring"
(interactive)
(let* ((file (dired-get-filename)))
(message file)))
#+end_src
#+begin_src emacs-lisp
(use-package dired-sidebar
:ensure t
:commands (dired-sidebar-toggle-sidebar)
:general
(general-def 'normal
"gD" 'dired-sidebar-toggle-sidebar)
(general-def 'normal dired-sidebar-mode-map
"l" 'dired-sidebar-find-file
"h" 'dired-sidebar-up-directory))
#+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 :tangle no
(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 fd-dired
:general
(chris/leader-keys
"sD" 'fd-dired))
#+end_src
#+begin_src emacs-lisp :tangle no
(use-package dired-rainbow
:after dired
:config
(defconst chris/dired-media-files-extensions
'("mp3" "mp4" "MP3" "MP4" "avi" "mpg" "flv" "ogg" "opus")
"Media files.")
(defconst chris/dired-image-files-extensions
'("png" "jpg" "PNG" "JPG" "jpeg" "JPEG" "gif" "GIF")
"image files")
(dired-rainbow-define html "#4e9a06" ("htm" "html" "xhtml"))
(dired-rainbow-define media "#f3f99d" chris/dired-media-files-extensions)
(dired-rainbow-define image "#5af78e" chris/dired-image-files-extensions)
(dired-rainbow-define log (:inherit default :italic t) ".*\\.log"))
#+end_src
#+begin_src emacs-lisp
(use-package diredfl
:after dired
:config (diredfl-global-mode +1))
#+end_src
#+begin_src emacs-lisp
(use-package dired-rsync
:general
(general-def 'normal dired-mode-map
"C" 'dired-rsync))
#+end_src
**** Dirvish
Let's try using dirvish as a kind of ranger
#+begin_src emacs-lisp :tangle no
(use-package dirvish
:after dired
:custom
(dirvish-bookmarks-alist
'(("h" "~/" "Home")
("d" "~/Downloads/" "Downloads")
("p" "~/Pictures/" "Pictures")
("D" "/dev/" "dev")
("t" "~/.local/share/Trash/files/" "TrashCan")))
:config
(dirvish-override-dired-mode)
;; (dirvish-peek-mode)
(setq dirvish-attributes '(all-the-icons file-size))
(dirvish-define-preview exa (file)
"Uses `exa' to generate directory preview."
(when (file-directory-p file) ; we only interest in directories here
`(shell . ("exa" "--color=always" "-al" ,file))))
(add-to-list 'dirvish-preview-dispatchers 'exa)
(set-face-attribute 'ansi-color-blue nil :foreground "#FFFFFF")
(defun chris/dirvish-quit ()
"quit a fullscreen dirvish if it's open, else do a normal kill buffer"
(interactive)
(if (dirvish-dired-p)
(dirvish-quit-h)
(with-current-buffer
(current-buffer)
(dirvish-toggle-fullscreen)
(dirvish-quit-h))))
:general
(chris/leader-keys 'normal 'override
"od" 'dirvish-dired :which-key "open dirvish here")
('normal 'dirvish-mode-map
"gf" 'dirvish-toggle-fullscreen
"RET" 'dirvish-find-file
"h" 'dirvish-up-directory
"l" 'dirvish-find-file
"b" 'dirvish-goto-bookmark
"?" 'dirvish-top-level-menu
"a" 'dirvish-file-info-menu
"A" 'dirvish-mark-actions-menu
"M" 'dirvish-marking-menu
"q" 'chris/dirvish-quit))
#+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))
(add-to-list 'tramp-remote-path 'tramp-own-remote-path)
(connection-local-set-profile-variables
'guix-system
'((tramp-remote-path . (tramp-own-remote-path))))
(connection-local-set-profiles
'(:application tramp :protocol "sudo" :machine "hostname")
'guix-system)
(setq tramp-default-proxies-alist '())
#+end_src
** Ledger
Ledger mode
#+begin_src emacs-lisp
(use-package ledger-mode
:config
(setq-mode-local ledger-mode
completion-at-point-functions (list
(cape-capf-super
#'cape-file
#'cape-abbrev))
tab-always-indent 'complete
completion-cycle-threshold t
ledger-complete-in-steps t)
(defun chris/ledger-clean ()
(interactive)
(when (eq major-mode #'ledger-mode)
(ledger-mode-clean-buffer)))
(add-hook 'after-save-hook #'chris/ledger-clean)
(setq ledger-default-date-format "%Y-%m-%d")
:general
(general-def 'normal ledger-mode-map
"ga" 'ledger-add-transaction
"gr" 'ledger-report
"gp" 'ledger-toggle-current
"C-j" 'ledger-navigate-next-xact-or-directive
"C-k" 'ledger-navigate-prev-xact-or-directive))
#+end_src
** MU4E
:PROPERTIES:
:ID: 20230619T060455.174705
:END:
#+begin_src emacs-lisp
(use-package mu4e
;; :load-path "~/.guix-home/profile/share/emacs/site-lisp/mu4e/"
:init
(setq mu4e-maildir "~/mail"
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/docs/attachments"
mu4e-completing-read-function #'completing-read
mu4e-notification-support t
;; mu4e-mu-binary "/home/chris/.guix-home/profile/bin/mu"
;; mu4e-mu-binary "/etc/profiles/per-user/chris/bin/mu"
mu4e-compose-signature-auto-include nil
mu4e-headers-fields
'((:human-date . 12)
(:flags . 10)
(:from . 22)
(:subject))
mu4e-headers-flagged-mark '("F" . "")
mu4e-headers-replied-mark '("R" . "")
mu4e-headers-personal-mark '("p" . "")
mu4e-headers-list-mark '("s" . ""))
:config
(setq mail-user-agent 'mu4e-user-agent)
(defun mu4e--main-action-str (name func)
"This seems to be needed until evil-collection supports the latest
version of mu4e."
"mu4e--main-action")
(remove-hook 'mu4e-main-mode-hook 'evil-collection-mu4e-update-main-view)
(setq mu4e-contexts
(list
(make-mu4e-context
:name "work"
: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")
(smtpmail-starttls-credentials . '(("smtp.office365.com" 587 nil nil)))
(smtpmail-auth-credentials . '(("smtp.office365.com" 587 "chris@tfcconnection.org" nil)))
(smtpmail-smtp-server . "smtp.office365.com")
(mu4e-compose-signature . "")))
(make-mu4e-context
:name "personal"
:match-func
(lambda (msg)
(when msg
(string-prefix-p "/cochrun" (mu4e-message-field msg :maildir))))
:vars '((user-mail-address . "chris@cochrun.xyz")
(mu4e-sent-folder . "/cochrun/Sent/")
(mu4e-drafts-folder . "/cochrun/Drafts")
(mu4e-trash-folder . "/cochrun/Trash")
(mu4e-refile-folder . "/cochrun/Archive")
(smtpmail-smtp-user . "chris@cochrun.xyz")
(smtpmail-starttls-credentials . '(("mail.cochrun.xyz" 587 nil nil)))
(smtpmail-auth-credentials . '(("mail.cochrun.xyz" 587 "chris@cochrun.xyz" nil)))
(smtpmail-smtp-server . "mail.cochrun.xyz")
(mu4e-compose-signature . "")))))
;; Add the ability to send email
(setq message-send-mail-function 'smtpmail-send-it
starttls-use-gnutls t
smtpmail-default-smtp-server "mail.cochrun.xyz"
smtpmail-smtp-service 587)
;; shortcuts in the jumplist by pressing "J" in the mu4e buffer
(setq mu4e-maildir-shortcuts
'((:maildir "/cochrun/Archive" :key ?a)
(:maildir "/cochrun/Inbox" :key ?i)
(:maildir "/office/Inbox" :key ?w)
(:maildir "/office/Junk Email" :key ?j)
(:maildir "/office/INBOX/Website Forms" :key ?f)
(:maildir "/office/Inbox/Money" :key ?m)
(: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\"0 AND NOT maildir:\"/outlook/Deleted\" AND NOT maildir:\"/office/Deleted Items\" AND NOT maildir:\"/office/Archive\" AND NOT maildir:\"/office/INBOX/Website Forms\" AND NOT maildir:\"/outlook/Archive\" AND NOT maildir:\"/cochrun/Archive\" AND NOT maildir:\"/cochrun/Junk\" AND NOT flag:list"
: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)
(:name "Lists"
:query "flag:list AND NOT flag:trashed"
:key 108)))
(setq
mu4e-view-prefer-html nil
shr-color-visible-luminance-min 80)
(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 ()
(display-line-numbers-mode -1)
(toggle-truncate-lines +1))
(defun chris/setup-mu4e-view ()
(display-line-numbers-mode -1)
(setq visual-fill-column-center-text t)
(setq visual-fill-column-width 120)
(visual-fill-column-mode +1)
(visual-line-mode -1)
(toggle-truncate-lines +1))
;; (remove-hook 'mu4e-main-mode-hook '(display-line-numbers-mode -1))
(add-hook 'mu4e-headers-mode-hook #'chris/setup-mu4e-headers)
(add-hook 'mu4e-headers-mode-hook #'chris/setup-mu4e-headers)
;; (add-hook 'mu4e-view-mode-hook #'chris/setup-mu4e-view)
(mu4e t)
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"om" 'mu4e)
(general-def 'normal mu4e-headers-mode-map
"f" 'mu4e-headers-mark-for-flag)
(general-def 'normal mu4e-view-mode-map
"ga" 'mu4e-view-save-attachments
"q" 'mu4e-view-quit))
#+end_src
# (use-package org-mime
# :after
# :ensure t)
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)
(defun org-msg-edit-mode-mu4e ()
"Setup mu4e faces, addresses completion and run mu4e."
(mu4e--compose-remap-faces)
(unless (mu4e-running-p)
(if (fboundp #'mu4e~start) (mu4e~start) (mu4e--start)))
(when mu4e-compose-complete-addresses
(mu4e--compose-setup-completion)))
(setq mail-user-agent 'mu4e-user-agent
org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil \\n:t"
org-msg-startup "hidestars indent inlineimages"
org-msg-greeting-fmt "\nHey%s\n\n"
org-msg-recipient-names '(("chris@tfcconnection.org" . "Chris Cochrun"))
org-msg-greeting-name-limit 3
org-msg-default-alternatives '((new . (text html))
(reply-to-html . (text html))
(reply-to-text . (text)))
org-msg-convert-citation t
org-msg-signature "#+begin_signature
*Chris*
/Praising God in all things!/
#+end_signature"))
#+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://thrillshare-cmsv2.services.thrillshare.com/api/v4/o/6958/cms/events/generate_ical?filter_ids&section_ids" "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
)))
(custom-set-faces
'(cfw:face-title ((t (:weight bold :height 2.0 :inherit fixed-pitch))))
'(cfw:face-header ((t (:slant italic :weight bold))))
'(cfw:face-sunday ((t :weight bold)))
'(cfw:face-saturday ((t :weight bold)))
'(cfw:face-holiday ((t :weight bold)))
;; '(cfw:face-grid ((t :foreground "DarkGrey")))
'(cfw:face-default-content ((t :height 0.7)))
;; '(cfw:face-periods ((t :foreground "cyan")))
'(cfw:face-day-title ((t (:background nil))))
'(cfw:face-default-day ((t :weight bold :inherit cfw:face-day-title)))
'(cfw:face-annotation ((t :inherit cfw:face-day-title)))
'(cfw:face-disable ((t :inherit cfw:face-day-title)))
'(cfw:face-today-title ((t :weight bold)))
'(cfw:face-today ((t :weight bold)))
;; '(cfw:face-select ((t :background "#2f2f2f")))
'(cfw:face-toolbar ((t :foreground "Steelblue4" :background "Steelblue4")))
'(cfw:face-toolbar-button-off ((t :weight bold)))
'(cfw:face-toolbar-button-on ((t :weight bold))))
(setq cfw:fchar-junction ?╋
cfw:fchar-vertical-line ?┃
cfw:fchar-horizontal-line ?━
cfw:fchar-left-junction ?┣
cfw:fchar-right-junction ?┫
cfw:fchar-top-junction ?┯
cfw:fchar-top-left-corner ?┏
cfw:fchar-top-right-corner ?┓)
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"oc" 'chris/calfw-calendar-open)
(general-def cfw:calendar-mode-map
"q" 'kill-current-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
** Org-Caldav-sync
I'd like to sync my org-files to caldav and hopefully use more native android apps to manage tasks and reminders.
#+BEGIN_SRC emacs-lisp :tangle no
(use-package org-caldav
:after org
:config
(setq org-caldav-url "https://staff.tfcconnection.org/remote.php/dav/calendars/chris"
org-caldav-calendar-id "org"
org-caldav-inbox "/home/chris/docs/notes/todo/inbox.org"
org-caldav-files '("/home/chris/docs/notes/todo/todo.org"
"/home/chris/docs/notes/todo/notes.org"
"/home/chris/docs/notes/todo/prayer.org")
org-icalendar-alarm-time 15
org-icalendar-use-scheduled '(todo-start event-if-todo)))
#+END_SRC
** Org Notifications
I'd really like to have notifications for when things are scheduled so that I get my butt to working.
#+BEGIN_SRC emacs-lisp :tangle no
(use-package org-notifications
:after org
:config
(setq org-notifications-notify-before-time 600)
(org-notifications-start))
#+END_SRC
#+BEGIN_SRC emacs-lisp :tangle no
(use-package org-wild-notifier
:after org
:config
(setq alert-default-style 'libnotify)
(org-wild-notifier-mode +1))
#+END_SRC
** Magit
Use magit, because why wouldn't you? duh!
#+begin_src emacs-lisp
(use-package magit
:ensure nil
: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 t
: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 "kaladin") (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 :height 0.7))
(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))))))
(setq eshell-banner-message "")
(setq eshell-path-env "/usr/local/bin:/usr/bin:/opt/android-sdk/cmdline-tools/latest/bin:/home/chris/.cargo/bin")
;; this makes it so flutter works properly
(setenv "ANDROID_SDK_ROOT" "/opt/android-sdk")
(setenv "CHROME_EXECUTABLE" "/usr/bin/qutebrowser")
(setenv "JAVA_HOME" "/usr/lib/jvm/default")
(add-hook 'eshell-mode-hook (lambda () (display-line-numbers-mode -1)))
(defun chris/upgrade-nix ()
"A function for updating my nix config"
(interactive)
(let* ((default-directory (file-truename (concat home-directory ".dotfiles/"))))
(async-shell-command
"sudo nixos-rebuild switch --show-trace --verbose --impure --flake .#"
"*upgrade*"
"*upgrade-errors*")))
(defun chris/eshell-current-window ()
"Open eshell in the current directory using the current window"
(interactive)
(same-window-prefix)
(eshell))
(setq eshell-command-aliases-list
`(("q" "exit")
("f" "find-file $1")
("ff" "find-file $1")
("d" "dired $1")
("bd" "eshell-up $1")
("rg" "rg --color=always $*")
("ll" "ls -lah $*")
("less" "view-file $1")
("gg" "magit-status")
("clear" "clear-scrollback")
("!!" "eshell-previous-input 2")
("yay" "paru $1")
("yeet" "paru -Rns $1")
("nixs" "nix search nixpkgs $1")
("myip" "curl icanhazip.com")
("ytd" "yt-dlp -o ~/Videos/%(title)s.%(ext)s $1")
("nupg" "upgrade-nix")
("nupd" "update-nix")
("dup" "sudo docker compose up -d")
("ddown" "sudo docker compose down")
("dpull" "sudo docker compose pull")
("dlogs" "sudo docker compose logs --follow")
("ksb" "/home/chris/dev/kde/src/kdesrc-build/kdesrc-build")
("ws" "rsync -avzP public/ chris@staff.tfcconnection.org:tfcconnection")
("wttr" "curl wttr.in/@home.cochrun.xyz")
("gh" "guix home -L ~/.dotfiles/guix reconfigure /home/chris/.dotfiles/guix/home.scm")
("gs" ,(concat "sudo guix system -L ~/.dotfiles/guix reconfigure /home/chris/.dotfiles/guix/" (system-name) ".scm"))))
(defun chris/eshell-new()
"Open a new eshell buffer"
(interactive)
(eshell 'N))
(defun chris/eshell-switch()
"interactively switch between eshell buffers"
(interactive)
(consult-buffer))
(defun chris/eshell-window ()
"Open a new emacs window with eshell as a sole buffer"
(interactive)
(with-selected-frame (make-frame '((name . "*eshell-terminal*")))
(setq eshell-terminal (eshell 100))
(display-buffer-same-window
(generate-new-buffer-name "*eshell-terminal*") '())
(eshell 100)
(other-window 1)
(switch-to-buffer "*eshell*<100>")
(other-window 1)
(delete-window)))
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"oe" 'chris/eshell-new
"oE" 'chris/eshell-current-window
"be" 'eshell)
(general-def '(normal insert) eshell-mode-map
"C-d" 'kill-buffer-and-window
"C-l" 'eshell/clear
"<up>" 'eshell-previous-input))
#+end_src
*** EAT
Emulate a terminal
#+begin_src emacs-lisp
(use-package eat
:init
(add-hook 'eshell-load-hook #'eat-eshell-mode))
#+end_src
*** Esh-autosuggest
#+begin_src emacs-lisp :tangle no
(use-package esh-autosuggest
:hook (eshell-mode . esh-autosuggest-mode)
:ensure nil)
#+end_src
** Vterm
#+begin_src emacs-lisp
(setq vterm-buffer-name-string "vterm %s"
vterm-shell "/run/current-system/sw/bin/nu")
(defun chris/vterm-setup ()
"Setup vterm with my preferred settings"
(display-line-numbers-mode -1))
(add-hook 'vterm-mode-hook 'chris/vterm-setup)
#+end_src
** PDF-Tools
:PROPERTIES:
:ID: 20230906T144438.725878
:END:
Let's use pdf-tools for a lot better interaction with pdfs.
#+begin_src emacs-lisp
(use-package pdf-tools
:mode ("\\.pdf\\'" . pdf-view-mode)
:init
;; (setq pdf-info-epdfinfo-program "/nix/store/6pc8vs42xbfah1h8h050a77p7vpr12kc-emacs-packages-deps/share/emacs/site-lisp/elpa/pdf-tools-20220823/epdfinfo"
;; pdf-tools-directory "/nix/store/6pc8vs42xbfah1h8h050a77p7vpr12kc-emacs-packages-deps/share/emacs/site-lisp/elpa/pdf-tools-20220823/")
(pdf-tools-install)
:config
(defun chris/print-multi-pdf ()
"Print the pdf in as many copies as needed"
(interactive)
(let* ((copies (completing-read "How many copies: " '("1" "2" "3")))
(sides (completing-read "Print both sides or one? " '("two sided" "one sided")))
(pdf-misc-print-program-args `("-o media=Letter" ,(format "-# %s" copies) "-o fitplot"
,(if (string= sides "two sided")
"-o sides=two-sided-long-edge"
""))))
(message "printing %s copies." copies)
(pdf-misc-print-document (buffer-file-name) t)))
(custom-set-variables '(pdf-misc-print-program-executable "lpr")
'(pdf-misc-print-program-args (quote ("-o media=Letter" "-o fitplot" "-o sides=two-sided-long-edge"))))
(add-hook 'pdf-view-mode-hook 'pdf-view-fit-page-to-window)
(defun chris/display-line-numbers-off ()
(display-line-numbers-mode -1))
(add-hook 'pdf-view-mode-hook 'chris/display-line-numbers-off)
:general
(general-def
:states 'normal
:keymaps 'pdf-view-mode-map
"C-p" 'chris/print-multi-pdf))
#+end_src
** EPUB
#+begin_src emacs-lisp
(use-package nov
:mode ("\\.epub\\'" . nov-mode)
:config
(defun chris/setup-nov-mode
(interactive)
(visual-fill-column-mode +1)
(display-line-numbers-mode -1)
(variable-pitch-mode +1)
(setq visual-fill-column-width 130
visual-fill-column-center-text t))
(add-hook 'nov-mode-hook 'chris/setup-nov-mode))
#+end_src
** EAF (Emacs Application Framework)
#+begin_src emacs-lisp :tangle no
(use-package eaf
:straight (:host github :repo "emacs-eaf/emacs-application-framework"
:files ("*.el" "*.py" "core" "*.json" "app" "*"))
:defer t
:custom
(eaf-browser-dark-mode t)
(eaf-browser-continue-where-left-off t)
(eaf-browser-enable-adblocker t))
#+end_src
** EMPV
With empv we can perhaps control mpv much more fine grainly and even search youtube videos easier through emacs. Let's set it up.
#+begin_src emacs-lisp
(use-package empv
:config
(setq empv-invidious-instance "https://inv.cochrun.xyz/api/v1"
empv-video-file-extensions '("mkv" "mp4" "avi" "mov" "webm")
empv-mpv-args '("--no-terminal" "--idle" "--input-ipc-server=/tmp/empv-socket")
empv-video-dir "~/vids")
(empv-embark-initialize-extra-actions)
(general-auto-unbind-keys)
(add-hook 'empv-youtube-results-mode-hook #'evil-normal-state)
(defun chris/empv-yt-dlp ()
"Download the current video and and play it"
(interactive)
(let* ((item (empv-youtube-results--current-item))
(video-id (alist-get 'videoId item))
(playlist-id (alist-get 'playlistId item))
(title (alist-get 'title item))
(url (format
"https://youtube.com/%s=%s"
(if video-id "watch?v" "playlist?list")
(or video-id playlist-id))))
(async-shell-command (concat
"yt-dlp" " -o '/home/chris/vids/%(title)s.%(ext)s'"
" \"" url "\""))
(let ((file (format "/home/chris/vids/%s.%s" title "webm")))
(empv-play file))
(message url)))
(defun chris/empv-yt-dlp-jellyfin ()
"Grabs the current video at point and downloads it to my jellyfin server"
(interactive)
(let* ((server "jelly.cochrun.xyz")
(item (empv-youtube-results--current-item))
(video-id (alist-get 'videoId item))
(playlist-id (alist-get 'playlistId item))
(title (alist-get 'title item))
(location (completing-read "location: " '("dev" "house" "ministry" "misc")))
(url (format
"https://youtube.com/%s=%s"
(if video-id "watch?v" "playlist?list")
(or video-id playlist-id))))
(async-shell-command (concat
"ssh " server " \"" "yt-dlp"
" -o '/storage/media/media/chris/extras/yt/"
location
"/%(title)s.%(ext)s'"
" '" url "'" "\""))
(message (format "%s is downloading to the %s folder on jellyfin" url location))))
(defun chris/empv-org-play-link (&optional link)
"Play link in empv from either supplied link or link at point in org-mode"
(interactive)
(let ((link (if link
link
(org-babel-read-link))))
(empv-play link)))
(defun chris/dired-empv-play-or-enqueue ()
"Play file at point in dired"
(interactive)
(let* ((file (dired-get-filename)))
(empv-play-or-enqueue file)))
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"vo" 'empv-play-or-enqueue
"vt" 'empv-toggle
"vv" 'empv-play-video
"vx" 'empv-chapter-select
"vy" 'empv-youtube-tabulated
"vn" 'empv-playlist-next
"vp" 'empv-playlist-prev
"vs" 'empv-playlist-select)
(general-def
:states 'normal
:keymaps 'empv-youtube-results-mode-map
"q" 'kill-current-buffer
"RET" 'empv-youtube-results-play-or-enqueue-current
"i" 'empv-youtube-results-inspect
"d" 'chris/empv-yt-dlp
"D" 'chris/empv-yt-dlp-jellyfin)
(general-def
:states 'normal
:keymaps 'dired-mode-map
"vi" 'chris/dired-empv-play-or-enqueue))
#+end_src
I need to update the new normal mode pieces to this...
Value
- negative-argument
0 nil
1 digit-argument
2 digit-argument
3 digit-argument
4 digit-argument
5 digit-argument
6 digit-argument
7 digit-argument
8 digit-argument
9 digit-argument
< beginning-of-buffer
<follow-link> mouse-face
<keymap> C-M-i backward-button
<keymap> TAB forward-button
<mouse-2> mouse-select-window
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> ignore
<normal-state> S tabulated-list-sort
<normal-state> Z Q evil-quit
<normal-state> Z Z quit-window
<normal-state> g h tabulated-list-previous-column
<normal-state> g l tabulated-list-next-column
<normal-state> q quit-window
<normal-state> q quit-window
<normal-state> { tabulated-list-narrow-current-column
<normal-state> } tabulated-list-widen-current-column
> end-of-buffer
? nil
DEL scroll-down-command
M-<left> tabulated-list-previous-column
M-<right> tabulated-list-next-column
P empv-youtube-results-play-current
RET empv-youtube-results-play-or-enqueue-current
S tabulated-list-sort
S-SPC scroll-down-command
SPC scroll-up-command
SPC..~ undefined
Y empv-youtube-results-copy-current
a empv-youtube-results-enqueue-current
c empv-youtube-results-show-comments
g r revert-buffer
h nil
i empv-youtube-results-inspect
j next-line
k previous-line
n nil
p nil
q quit-window
{ tabulated-list-narrow-current-column
} tabulated-list-widen-current-column
** Elfeed
:PROPERTIES:
:ID: 20240109T071148.947001
:END:
#+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-empv-play ()
"Play elfeed video or podcast link in empv"
(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
(if (string= enclosure nil)
link
enclosure)))
(final-url (if (string-prefix-p "https://inv.cochrun.xyz/" url)
(string-replace "https://inv.cochrun.xyz/" "https://youtube.com/" url)
url))
(title (elfeed-entry-title entry)))
(empv-play-or-enqueue final-url)
(message "Playing: %s from %s" title final-url)
(elfeed-search-untag-all-unread)))
(defun chris/empv-ytdlp-play-or-enqueue (process signal video)
"play or enqueue a video after ytdlp is done"
(when (memq (process-status process) '(exit signal)
(empv-play-or-enqueue video)
(shell-command-sentinel process signal))))
(defun chris/elfeed-ytdlp-empv ()
"Download the video at point and add to empv"
(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
(if (string= enclosure nil)
link
enclosure)))
(title (elfeed-entry-title entry))
(output-buffer (generate-new-buffer "*yt-dlp*"))
(proc (progn
(message url)
(make-process
:name "yt-dlp"
:buffer output-buffer
:command `("yt-dlp"
"-o" "/home/chris/vids/%(title)s.%(ext)s"
,(cl-coerce url 'string)))
(get-buffer-process output-buffer))))
(elfeed-search-untag-all-unread)
(if (process-live-p proc)
(set-process-sentinel
proc `(empv-play-or-enqueue
,(concat "/home/chris/vids/" title ".webm")))
(message "No process running"))))
(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
(if (string= enclosure nil)
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-insert-link ()
"Inserts the hovered link into bongo for playback in mpv"
(interactive)
(let* ((link (shr--current-link-region))
(entry elfeed-show-entry)
(title (elfeed-entry-title entry))
(url (get-text-property (point) 'shr-url))
(bongo-pl chris/elfeed-bongo-playlist)
(buffer (get-buffer-create bongo-pl)))
(message "url is %s" url)
(message "title is %s" title)
(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 item “%s” in %s"
(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'."))))
(setq elfeed-search-filter "@6-days-ago +unread")
(defun chris/elfeed-ui-setup ()
(display-line-numbers-mode -1)
(toggle-truncate-lines +1)
(elfeed-search-fetch))
(defun chris/elfeed-show-ui-setup ()
(display-line-numbers-mode -1)
(setq visual-fill-column-width 130
visual-fill-column-center-text t)
(toggle-truncate-lines -1)
(hide-mode-line-mode +1)
(visual-fill-column-mode +1))
(add-hook 'elfeed-search-mode-hook 'chris/elfeed-ui-setup)
(add-hook 'elfeed-show-mode-hook 'chris/elfeed-show-ui-setup)
(setq shr-use-colors nil)
(defun chris/efleed-org-view ()
"Display the url of the entry as an org-mode buffer"
(interactive)
(let ((link (elfeed-entry-link elfeed-show-entry)))
(message link)
(org-web-tools-read-url-as-org link)))
(defun chris/elfeed-eww-browse ()
(interactive)
(let* ((entry (elfeed-search-selected :ignore-region))
(link (elfeed-entry-link entry)))
(message link)
(eww link)))
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"of" 'elfeed)
(general-def 'normal elfeed-search-mode-map
"v" 'chris/elfeed-empv-play
"b" 'chris/elfeed-eww-browse
"o" 'chris/elfeed-org-view
"d" 'chris/elfeed-ytdlp-empv)
(general-def 'normal elfeed-show-mode-map
"o" 'chris/elfeed-org-view
"b" 'eww))
#+end_src
#+begin_src emacs-lisp :tangle no
(use-package elfeed-protocol
:ensure t
:config
(setq elfeed-feeds '(("owncloud+https://chris@nc.cochrun.xyz"
:password (s-trim
(shell-command-to-string "rbw get 'nextcloud home'")))))
(setq elfeed-protocol-enabled-protocols '(fever newsblur owncloud ttrss))
(elfeed-protocol-enable))
#+end_src
#+begin_src emacs-lisp
(use-package elfeed-org
:after elfeed
:config
(setq rmh-elfeed-org-files (list "~/docs/elfeed.org"))
(elfeed-org)
(elfeed-update))
#+end_src
** Bongo
#+begin_src emacs-lisp
(use-package bongo
:commands (bongo bongo-playlist-buffer)
:config
(setq bongo-default-directory "~/music"
bongo-prefer-library-buffers nil
bongo-insert-whole-directory-trees t
bongo-logo nil
bongo-display-playback-mode-indicator t
bongo-display-inline-playback-progress t
bongo-mpv-remote-option "--input-ipc-server=/tmp/bongo-mpv.socket"
bongo-field-separator (propertize " · " 'face 'shadow))
(define-bongo-backend mpv
:constructor 'bongo-start-mpv-player
:program-name 'mpv
:program-arguments '("--profile=fast")
:extra-program-arguments nil
:matcher '((local-file "file:" "http:" "ftp:" "lbry:")
"mpg" "mpeg" "vob" "avi" "ogm" "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))
(define-bongo-backend mpv-music
:constructor 'bongo-start-mpv-player
:program-name-variable 'mpv
:extra-program-arguments '("--profile=slow --input-ipc-server=/tmp/mpvsocket")
:matcher '((local-file "file:" "http:" "ftp:" "lbry:")
"mka" "wav" "wma" "ogm" "opus"
"ogg" "flac" "mp3" "mka" "wav")
:matcher '(("http:" "https:" "lbry:") . t))
(setq bongo-custom-backend-matchers '((mpv-music local-file
"mka" "wav" "wma" "ogm" "opus"
"ogg" "flac" "mp3" "mka" "wav")
(mpv local-file
"mpg" "mpeg" "vob" "avi" "ogm" "mp4"
"mkv" "mov" "asf" "wmv" "rm" "rmvb" "ts")))
(setq bongo-enabled-backends '(mpv mpv-music)
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))
(defun chris/bongo-mpv-speed-up ()
(interactive)
(bongo--run-mpv-command bongo-player "speed" "set" "speed" "1.95"))
(defun chris/bongo-open-elfeed-queue-buffer ()
(interactive)
(display-buffer "*Bongo-Elfeed Queue*"))
(defun chris/bongo-ytdlp ()
"Download the video or item using yt-dlp"
(interactive)
(while bongo-uri-p
(message bongo-file-name)))
:general
(chris/leader-keys
"ob" 'bongo
"oB" 'chris/bongo-open-elfeed-queue-buffer
"mi" 'bongo-insert-enqueue
"mp" 'bongo-pause/resume)
(general-def 'normal bongo-playlist-mode-map
"RET" 'bongo-dwim
"d" 'bongo-kill-line
"u" 'bongo-unmark-region
"p" 'bongo-pause/resume
"P" 'bongo-yank
"H" 'bongo-switch-buffers
"q" 'bury-buffer
"+" 'chris/bongo-mpv-speed-up
"m" 'chris/bongo-mark-line-forward)
(general-def 'normal bongo-library-mode-map
"RET" 'bongo-dwim
"d" 'bongo-kill-line
"u" 'bongo-unmark-region
"e" 'bongo-insert-enqueue
"p" 'bongo-pause/resume
"H" 'bongo-switch-buffers
"q" 'bury-buffer))
#+end_src
** EMMS
Since Bongo seems kinda difficult I shall give EMMS another try.
#+begin_src emacs-lisp
(use-package emms
:config
(emms-all)
(evil-collection-emms-setup)
(setq emms-player-list '(emms-player-mpv)
emms-player-mpv-parameters '("--quiet" "--really-quiet" "--no-audio-display" "--speed=1"))
(setq emms-source-file-default-directory "~/music/"
emms-tag-editor-tag-ogg-program "mid3v2")
(defun chris/emms-delete-song ()
"Deletes files in the emms browser by default. Maybe I'll have a yes or no thingy..."
(interactive)
(if (yes-or-no-p "delete the file too?")
(emms-browser-remove-tracks t)
(emms-browser-remove-tracks)))
(defun chris/emms-mpv-video-playlist ()
"Create a playlist for mpv videos."
(interactive)
(emms-playlist-new "*emms-videos*"))
(defun chris/emms-add-video (&optional video)
"adds the given video to the emms playlist"
(interactive)
(if video
(emms-add-url video)
(emms-add-url)))
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"mo" 'emms
"ml" 'emms-browser
"mp" 'emms-pause
"ma" 'emms-add-dired
"mr" 'emms-toggle-repeat-track
"mn" 'emms-next
"mb" 'emms-previous
"m]" 'emms-seek-forward
"m[" 'emms-seek-backward)
(general-def 'normal emms-playlist-mode-map
"q" 'bury-buffer
"d" 'emms-playlist-mode-kill-track
"m" 'emms-mark-track
"D" 'emms-playlist-mode-goto-dired-at-point
"]" 'emms-seek-forward
"[" 'emms-seek-backward
"C-k" 'emms-mark-kill-marked-tracks
"p" 'emms-pause)
(general-def 'normal emms-browser-mode-map
"q" 'bury-buffer
"d" 'chris/emms-delete-song
"D" 'emms-browser-view-in-dired))
#+end_src
** Transmission
I use transmission on a server to manage my torrents
#+begin_src emacs-lisp
(use-package transmission
:commands (transmission)
:config
(if (string-equal (system-name) "kaladin")
(setq transmission-host "home.cochrun.xyz"
transmission-rpc-path "/transmission/rpc"
transmission-refresh-modes '(transmission-mode
transmission-files-mode
transmission-info-mode
transmission-peers-mode))
(setq transmission-host "192.168.1.2"
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
** HASS
Hass is a package to control Home Assistant. I use HA so... I like HASS.
#+begin_src emacs-lisp :tangle no
(use-package hass
:init
(setq hass-host "home.cochrun.xyz"
hass-port "443"
hass-apikey "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiI2NDE1YTU5NzAzZDA0NDY0YTM0MmYzNTI1MjU0NjMwNyIsImlhdCI6MTY1Mjc1NjcwOSwiZXhwIjoxOTY4MTE2NzA5fQ.gGuxtwE2Xc1nGSgqpHiT2us-q04GWjtqnpgSHxvXgNI")
(hass-setup)
:general
(chris/leader-keys
"oh" 'hass-dash-open))
#+end_src
** Pass
I like to keep my passwords in password-store for a very unixy way of doing things.
#+begin_src emacs-lisp
(use-package auth-source-pass
:defer t
:config (auth-source-pass-enable))
#+end_src
#+begin_src emacs-lisp
(use-package pass
:defer t)
#+end_src
#+begin_src emacs-lisp
(use-package password-store
:after pass
:general
(chris/leader-keys
"sp" 'password-store-copy))
#+end_src
#+begin_src emacs-lisp
(use-package password-store-otp
:after password-store
:general
(chris/leader-keys
"st" 'password-store-otp-token-copy))
#+end_src
** Matrix/Ement
Matrix.el is a decent enough matrix client built in emacs. Like it.
#+begin_src emacs-lisp
(use-package plz
:ensure t)
(use-package ement
:ensure t
:config
(setq ement-room-images t
ement-save-sessions t)
:general
(general-def 'normal ement-room-mode-map
"q" 'bury-buffer
"RET" 'ement-room-send-message
"r" 'ement-room-send-reply
"gr" 'ement-room-sync
"R" 'ement-room-send-reaction)
(chris/leader-keys
"oM" 'ement-list-rooms
"el" 'ement-list-rooms
"ev" 'ement-view-room
"es" 'ement-directory-search
"em" 'ement-send-direct-message
"en" 'ement-notifications))
(ement-connect :uri-prefix "http://127.0.0.1:8008" :user-id "@chriscochrun:tfcconnection.org" :password "UtK4ik#sRx^GVqr@J3YVZ@#m")
#+end_src
** Mastodon
I'd like to access the mastodon world through emacs too.
#+begin_src emacs-lisp
(use-package mastodon
:config
(setq mastodon-instance-url "https://mastodon.online"
mastodon-active-user "chriscochrun")
:general
(chris/leader-keys
"oF" 'mastodon)
(general-def 'normal mastodon-mode-map
:states 'normal
"q" 'bury-buffer
"p" 'mastodon-toot
"r" 'mastodon-tl--update
"b" 'mastodon-toot--toggle-boost
"H" 'mastodon-tl--get-home-timeline
"F" 'mastodon-tl--get-federated-timeline
"v" 'chris/elfeed-bongo-insert-item
"N" 'mastodon-notifications--timeline))
#+end_src
** ActivityWatch
I like to track my time with ActivityWatch so I can notice and kill bad habits. At least I used to but it is pretty heavy on resources and doesn't work on Wayland.
#+begin_src emacs-lisp :tangle no
(use-package activity-watch-mode
:init
(if (string-equal (system-name) "syl")
(global-activity-watch-mode -1)
(global-activity-watch-mode -1)))
#+end_src
** LanguageTool
I am going to try and use LanguageTool to fix grammatical issues.
#+begin_src emacs-lisp :tangle no
(use-package languagetool
:ensure t
:defer t
:commands (languagetool-check
languagetool-clear-suggestions
languagetool-correct-at-point
languagetool-correct-buffer
languagetool-set-language
languagetool-server-mode
languagetool-server-start
languagetool-server-stop)
:config
(setq languagetool-java-arguments '("-Dfile.encoding=UTF-8")
languagetool-console-command "/home/chris/.emacs.d/languagetool/languagetool-commandline.jar"
languagetool-server-command "/home/chris/.emacs.d/languagetool/languagetool-server.jar"))
#+end_src
** qrencode
#+begin_src emacs-lisp :tangle no
(use-package qrencode)
#+end_src
** Org Bible
:PROPERTIES:
:ID: 20231221T141041.368526
:END:
Org Bible is going to be set of functions for creating and using a bible app within Emacs. Let's see if we can't make it work.
#+begin_src emacs-lisp
(load (concat user-emacs-directory "bible.el"))
(add-to-list 'warning-suppress-types '(org-element))
(add-to-list 'warning-suppress-log-types '(org-element))
#+end_src
** Emacs as RPG
:PROPERTIES:
:ID: 20240826T140351.934101
:END:
Using emacs to run rpg things
#+begin_src emacs-lisp
(add-to-list 'load-path "/home/chris/dev/emacs-rpgdm/")
(use-package rpgdm
:config
(defun chris/dnd-skill-check (&optional skill)
"Roll for a skill check using skill or prompting for one."
(interactive)
(let* ((skills (with-current-buffer
(find-file-noselect "~/docs/notes/20240804T132141--dungeons-and-dragons__area_fun_personal.org")
(org-table-get-remote-range
"skills" "@I$1..@II$2")))
(skill (completing-read "Which skill: "
(seq-filter
'stringp
(cl-loop for e in skills
collect
(if (= 0 (string-to-number
(substring-no-properties e)))
(substring-no-properties e))))))
(value (substring-no-properties
(nth (+ 1 (cl-position
skill skills
:test 'string=))
skills))))
(message value)))
:general
(chris/leader-keys
:states 'normal
:keymaps 'override
"a" '(:ignore t :which-key "RPGDM")
"ar" '(rpgdm-roll :which-key "roll")
"aa" '(hydra-rpgdm/body :which-key "hydra")))
#+end_src
** Performance
Here we have a bunch of performance tweaks
#+begin_src emacs-lisp
;; Reduce rendering/line scan work for Emacs by not rendering cursors or regions
;; in non-focused windows.
(setq-default cursor-in-non-selected-windows nil)
(setq highlight-nonselected-windows nil)
;; More performant rapid scrolling over unfontified regions. May cause brief
;; spells of inaccurate syntax highlighting right after scrolling, which should
;; quickly self-correct.
(setq fast-but-imprecise-scrolling t)
;; Don't ping things that look like domain names.
(setq ffap-machine-p-known 'reject)
;; Emacs "updates" its ui more often than it needs to, so we slow it down
;; slightly from 0.5s:
(setq idle-update-delay 1.0)
;; Font compacting can be terribly expensive, especially for rendering icon
;; fonts on Windows. Whether disabling it has a notable affect on Linux and Mac
;; hasn't been determined, but we inhibit it there anyway. This increases memory
;; usage, however!
;; (setq inhibit-compacting-font-caches t)
;; Introduced in Emacs HEAD (b2f8c9f), this inhibits fontification while
;; receiving input, which should help with performance while scrolling.
(setq redisplay-skip-fontification-on-input t)
#+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 (* 32 1024 1024))
(setq garbage-collection-messages nil)
#+end_src
Let's also use an automatic garbage collector while idle to make better input.
#+begin_src emacs-lisp
(use-package gcmh
:ensure t
:init
(gcmh-mode)
:config
(setq gcmh-idle-delay 2
gcmh-high-cons-threshold (* 128 1024 1024) ; 128mb
gcmh-verbose nil))
#+end_src
** Logging
Using Emacs 28 there are a lot of comp warnings so I am suppressing those for a quieter compilation.
#+begin_src emacs-lisp
(setq warning-suppress-types '((comp)))
#+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
(setq package-enable-at-startup nil)
;; ;; `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