#+PROPERTY: header-args:elisp :tangle ./init.el :results none
#+begin_quote
1. Clone this repo to ~/.emacs.d
2. Open =readme.org= in emacs
3. Press =C-c C-v t= to tangle all the blocks
4. Restart Emacs.
#+end_quote
This setup make certain assumptions regarding its environment:
1. Machine in use is Linux or MacOS
2. Shell in use is =fish=
* Spook max π»
:PROPERTIES:
:ID: D06520AB-2147-4FA7-83D4-6F3349B4743C
:END:
- Feature Flags
Feature flags to change the configuration based on different scenarios. Right now I run my Emacs
on two machines, but instead for adding checks for which machine I am on right now, I want to
create a default configuration, and modify it based on which feature-flags are enabled.
A profile is a cons cell of =(name . metadata)=
#+begin_src elisp
(defvar my--feature-flags '())
#+end_src
- On mac without external monitor
#+begin_src elisp
(when (eq system-type 'darwin)
(add-to-list 'my--feature-flags '(small-screen . t)))
#+end_src
- When Emacs' packages are handled by guix
#+begin_src elisp
(when (file-exists-p "/gnu/store")
(add-to-list 'my--feature-flags '(powered-by-guix . t)))
#+end_src
- When Emacs' packages are handled by straight.el
#+begin_src elisp
(unless (assoc 'powered-by-guix my--feature-flags)
(add-to-list 'my--feature-flags '(powered-by-straightel . t)))
#+end_src
- Bootstrap
#+begin_src elisp :tangle ./early-init.el
;; -*- lexical-binding: t -*-
;;; Bootstrap
;; Contrary to what many Emacs users have in their configs, you don't need
;; more than this to make UTF-8 the default coding system:
(set-language-environment "UTF-8")
;; set-language-enviornment sets default-input-method, which is unwanted
(setq default-input-method nil
package-enable-at-startup nil)
#+end_src
#+begin_src elisp :tangle ./init.el
;; -*- lexical-binding: t -*-
(when (assoc 'powered-by-guix my--feature-flags)
;; Must use :ensure nil until this issue is fixed
;; https://codeberg.org/guix/guix/issues/736
(setq use-package-ensure-function (lambda () nil)))
;; Bootstrap straight.el
(when (assoc 'powered-by-straightel my--feature-flags)
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name
"straight/repos/straight.el/bootstrap.el"
(or (bound-and-true-p straight-base-dir)
user-emacs-directory)))
(bootstrap-version 7))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage)))
;; use straight with use-package by default
(when (assoc 'powered-by-straightel my--feature-flags)
(setf straight-use-package-by-default t))
#+end_src
** Helper utilities
:PROPERTIES:
:ID: 675D81B1-9A5A-44E8-BA29-888C967974F9
:END:
- Ergonomically create keymaps
#+begin_src elisp
(defmacro my--defkeymap (name prefix &rest bindings)
"Create a new NAME-keymap bound to PREFIX, with BINDINGS.
Usage:
(my--defkeymap \"my-git\" \"C-c g\"
'(\"s\" . magit-status))
"
(let* ((keymap-name (intern (concat name "-keymap")))
(keymap-alias (intern name))
(keymap-bindings (mapcar
(lambda (binding)
(let ((binding (eval binding)))
`(define-key ,keymap-name (kbd ,(car binding)) #',(cdr binding))))
bindings)))
`(progn
(defvar ,keymap-name (make-sparse-keymap))
(defalias ',keymap-alias ,keymap-name)
(global-set-key (kbd ,prefix) ',keymap-alias)
,@keymap-bindings)))
#+end_src
- Utilities for helping with scratch buffer
This exactly function was introduced at or before Emacs version 29.1.
#+begin_src elisp
(defun my--get-or-create-scratch ()
"Switch to *scratch* buffer. Create if it doesn't already exist"
(interactive)
(let ((s-buf (get-buffer "*scratch*")))
(switch-to-buffer (get-buffer-create "*scratch*"))
(when (not s-buf) (emacs-lisp-mode))))
#+end_src
For firefox, I want to capture whatever I am reading and open the captured
content in baby-window. But there is a fuck-up here that I've been unable to
fix. On aborting the capture, the window layout gets messed up.
#+begin_src elisp
(defun my--baby-window (&optional baby-size)
"Create a baby of active window of BABY-SIZE height.
A baby-window is a small window below active-window, like
DevConsole in a browser. Depending on the active-window,
baby-window contains a different application. If the prefix-arg
is nil, baby-window always open *scratch* buffer."
(interactive)
(let ((baby-window (split-window-below (or baby-size -20))))
(select-window baby-window)
(my--get-or-create-scratch)))
#+end_src
** Preliminary setup
:PROPERTIES:
:ID: 704db7c8-f339-48cc-8e2c-d680da5899fd
:END:
- Start emacs as a server
#+begin_src elisp
(server-start)
#+end_src
- Unset annoying keybindings
#+begin_src elisp
(global-unset-key (kbd "C-x C-z"))
(global-unset-key (kbd "C-z"))
(global-unset-key (kbd "C-h h"))
#+end_src
- Set a custom-file so Emacs won't put customized entries in my =init.el= which
gets overwritten every time I tangle spookmax.d
#+BEGIN_SRC elisp
(setq custom-file (concat user-emacs-directory "custom.el"))
#+END_SRC
- Disable the ugly-ass toolbar, scroll-bars and menu-bar
#+begin_src elisp :tangle ./init.el
(setq inhibit-startup-screen t
ring-bell-function #'ignore
use-dialog-box nil)
(tool-bar-mode -1)
(scroll-bar-mode -1)
(menu-bar-mode -1)
(tooltip-mode -1)
#+end_src
- Make emacs a little transparent
#+begin_src elisp :tangle ./init.el
(set-frame-parameter (selected-frame) 'alpha '(98 . 95))
(add-to-list 'default-frame-alist '(alpha . (98 . 95)))
#+end_src
- Disable native-comp warnings
#+begin_src elisp
(setq native-comp-async-report-warnings-errors 'silent)
#+end_src
- UI fixes copied from Doom
https://github.com/hlissner/doom-emacs/blob/develop/core/core-ui.el
- Scrolling
#+begin_src elisp
;;; Scrolling
(setq hscroll-margin 2
hscroll-step 1
;; Emacs spends too much effort recentering the screen if you scroll the
;; cursor more than N lines past window edges (where N is the settings of
;; `scroll-conservatively'). This is especially slow in larger files
;; during large-scale scrolling commands. If kept over 100, the window is
;; never automatically recentered.
scroll-conservatively 101
scroll-margin 0
scroll-preserve-screen-position t
;; Reduce cursor lag by a tiny bit by not auto-adjusting `window-vscroll'
;; for tall lines.
auto-window-vscroll nil
;; mouse
mouse-wheel-scroll-amount '(2 ((shift) . hscroll))
mouse-wheel-scroll-amount-horizontal 2)
#+end_src
- Cursors
#+begin_src elisp
;;; Cursor
(blink-cursor-mode -1)
;; Don't blink the paren matching the one at point, it's too distracting.
(setq blink-matching-paren nil)
;; Don't stretch the cursor to fit wide characters, it is disorienting,
;; especially for tabs.
(setq x-stretch-cursor nil)
#+end_src
- Window/Frame
#+begin_src elisp
;; A simple frame title
(setq frame-title-format '("%b")
icon-title-format frame-title-format)
;; Don't resize the frames in steps; it looks weird, especially in tiling window
;; managers, where it can leave unseemly gaps.
(setq frame-resize-pixelwise t)
;; But do not resize windows pixelwise, this can cause crashes in some cases
;; when resizing too many windows at once or rapidly.
(setq window-resize-pixelwise nil)
;; Favor vertical splits over horizontal ones. Monitors are trending toward
;; wide, rather than tall.
(setq split-width-threshold 160
split-height-threshold nil)
#+end_src
- Minibuffer
#+begin_src elisp
;;
;;; Minibuffer
;; Allow for minibuffer-ception. Sometimes we need another minibuffer command
;; while we're in the minibuffer.
(setq enable-recursive-minibuffers t)
;; Show current key-sequence in minibuffer ala 'set showcmd' in vim. Any
;; feedback after typing is better UX than no feedback at all.
(setq echo-keystrokes 0.02)
;; Expand the minibuffer to fit multi-line text displayed in the echo-area. This
;; doesn't look too great with direnv, however...
(setq resize-mini-windows 'grow-only)
;; Typing yes/no is obnoxious when y/n will do
(setf use-short-answers t)
;; Try to keep the cursor out of the read-only portions of the minibuffer.
(setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt))
(add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
;; Don't resize the frames in steps; it looks weird, especially in tiling window
;; managers, where it can leave unseemly gaps.
(setq frame-resize-pixelwise t)
;; But do not resize windows pixelwise, this can cause crashes in some cases
;; when resizing too many windows at once or rapidly.
(setq window-resize-pixelwise nil)
#+end_src
- Allow selection to be deleted, generally expected behavior during
editing. I tried to not have this on by default, but I am finding
that to be increasingly annoying.
#+begin_src elisp
(delete-selection-mode +1)
#+end_src
- Indentation and whitespace
#+begin_src elisp
(setq my--indent-width 2)
(setq-default tab-width my--indent-width)
(setq-default indent-tabs-mode nil)
#+end_src
From: https://github.com/susam/emfy/blob/main/.emacs#L26
#+begin_src elisp
(setq-default indicate-empty-lines t)
(setq-default indicate-buffer-boundaries 'left)
;; Consider a period followed by a single space to be end of sentence.
(setq sentence-end-double-space nil)
(setq create-lockfiles nil)
#+end_src
I got sick of manually calling whitespace cleanup all the trim.
Cleanup whitespace.
#+begin_src elisp
(use-package whitespace-cleanup-mode
:config
(global-whitespace-cleanup-mode +1))
#+end_src
- Fill column for auto-formatting/filling paragraphs.
#+begin_src elisp
(setq-default fill-column 80)
#+end_src
- Introspection
:PROPERTIES:
:ID: e17d83de-251c-4407-b2ea-ca9c428e5ea1
:END:
Setup =which-key= for easy keys discovery
#+begin_src elisp
(use-package which-key
:config
(which-key-mode t))
#+end_src
- Highlighting
:PROPERTIES:
:ID: 79c1e2a9-c52e-4660-ba70-f6f1f98f7d4e
:END:
#+begin_src elisp
(global-hl-line-mode +1)
(use-package highlight-symbol
:hook (prog-mode . highlight-symbol-mode)
:config
(setq highlight-symbol-idle-delay 0.3))
#+end_src
- Line numbers
:PROPERTIES:
:ID: 2b554619-a8c0-4bd0-8ab0-8107c52a6e7e
:END:
#+begin_src elisp
(global-display-line-numbers-mode 1)
#+end_src
- Fix control characters in compilation-mode
Otherwise weird characters appear when fancy-ass CLI tools are executed.
#+begin_src elisp
(add-hook 'compilation-filter-hook 'ansi-color-compilation-filter)
#+end_src
- Window management
- Custom window keybindings
#+begin_src elisp
(my--defkeymap "my-windows" "C-c s-w"
'("-" . split-window-below)
'("_" . my--baby-window)
'("/" . split-window-right)
'("d" . delete-window)
'("m" . delete-other-windows)
'("o" . other-window)
'("h" . windmove-left)
'("j" . windmove-down)
'("k" . windmove-up)
'("l" . windmove-right)
'("w" . ace-window))
#+end_src
- Install [[https://github.com/abo-abo/ace-window][ace-window]] for some nice utilities.
#+begin_src elisp
(defun my--aw-kill-buffer-in-window (win)
"Kill the buffer shown in window WIN."
(kill-buffer (window-buffer win)))
(defun my--aw-kill-buffer-and-window (win)
"Kill the buffer shown in window WIN and window itself."
(kill-buffer (window-buffer win))
(delete-window win))
(use-package ace-window
:config
(setq aw-dispatch-always t)
(global-set-key (kbd "C-c w") 'ace-window)
(setq aw-dispatch-alist
'((?d my--aw-kill-buffer-in-window "Kill buffer in window")
(?s aw-swap-window "Swap Windows")
(?S aw-move-window "Move Window")
(?c aw-copy-window "Copy Window")
(?w aw-flip-window)
(?b aw-switch-buffer-in-window "Select Buffer")
(?B aw-switch-buffer-other-window "Switch Buffer Other Window")
(?k aw-delete-window "Delete Window")
(?K my--aw-kill-buffer-and-window "Kill buffer in window")
(?= aw-split-window-fair "Split Fair Window")
(?- aw-split-window-vert "Split Vert Window")
(?/ aw-split-window-horz "Split Horz Window")
(?m delete-other-windows "Delete Other Windows")
(?? aw-show-dispatch-help))
aw-keys '(?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)))
#+end_src
- Buffer management
#+begin_src elisp
(my--defkeymap
"my-buffers" "C-c b"
'("b" . switch-to-buffer)
'("n" . next-buffer)
'("p" . previous-buffer)
'("n" . next-buffer)
'("d" . kill-current-buffer)
'("s" . my--get-or-create-scratch))
#+end_src
- Font size
#+begin_src elisp
(defvar my--font-size 11)
(when (assoc 'small-screen my--feature-flags)
(setq my--font-size 14))
(set-face-attribute 'default nil :height (* 10 my--font-size))
#+end_src
- [Ma]git
Magit uses =project-switch-commands= which are present only in more
recent project.el project.
#+begin_src elisp
(use-package project
:config (setq popper-group-function #'popper-group-by-project))
(use-package magit)
#+end_src
- [[https://github.com/sshaw/git-link][git-link]] so I can copy link to lines in files because evidently, I am doing that a lot
#+begin_src emacs-lisp
(use-package git-link
:config
(setf git-link-use-commit t))
#+end_src
- Buncha nice keybindings.
#+begin_src elisp
(my--defkeymap "my-git" "C-c g"
'("s" . magit-status)
'("b" . magit-blame)
'("g" . magit-dispatch))
#+end_src
- Magit Forge
#+begin_src elisp
;; (use-package forge
;; :after magit)
#+end_src
- Ability to select both parts from a diff (from [[https://emacs.stackexchange.com/questions/19339/how-to-use-both-variants-in-ediff][SO]])
#+begin_src elisp
(defun my--ediff-copy-both-to-C ()
(interactive)
(ediff-copy-diff ediff-current-difference nil 'C nil
(concat
(ediff-get-region-contents ediff-current-difference 'A ediff-control-buffer)
(ediff-get-region-contents ediff-current-difference 'B ediff-control-buffer))))
(defun add-d-to-ediff-mode-map () (define-key ediff-mode-map "d" 'my--ediff-copy-both-to-C))
(add-hook 'ediff-keymap-setup-hook 'add-d-to-ediff-mode-map)
#+end_src
- Keep backup/auto-save files out of my vc
#+begin_src elisp
(setq
backup-dir "~/.emacs.d/bakups"
backup-directory-alist `((".*" . ,backup-dir))
auto-save-file-name-transforms `((".*" ,backup-dir t))
create-lockfiles nil)
#+end_src
- Setup PATH from shell
#+begin_src elisp
(use-package exec-path-from-shell
:config
(exec-path-from-shell-initialize))
#+end_src
** Libraries
:PROPERTIES:
:ID: 23e29ffc-70a8-4882-8a9b-86d66908e157
:END:
Libraries used elsewhere.
#+begin_src elisp
(use-package plz)
#+end_src
** Org mode
:PROPERTIES:
:ID: 8b2528d8-3fd2-4076-8b1e-791df8ed9a67
:END:
#+begin_src elisp
(use-package org
:config
(eval-after-load 'org-mode
(org-link-set-parameters
"yt"
:follow #'my-org--follow-yt-link
:export #'my-org--export-yt-link))
(add-hook
'org-mode-hook
(lambda () (display-line-numbers-mode -1))))
#+end_src
- Other settings
#+begin_src elisp
(setq
org-startup-indented t
org-startup-folded nil
org-agenda-window-setup "only-window"
org-directory "~/Documents/org"
org-agenda-diary-file (concat org-directory "/diary.org.gpg")
org-inbox-file (concat org-directory "/TODOs.org")
org-agenda-files (list org-inbox-file
(expand-file-name "work/on.org" org-directory))
;;Todo keywords I need
org-todo-keywords '((sequence "TODO(t!)" "DOING(n!)" "|" "DONE(d!)" "CANCELED(c@)"))
org-todo-keyword-faces '(("DOING" . "DeepSkyBlue")
("CANCELED" . org-done))
org-default-notes-file (concat org-directory "/refile.org")
org-refile-targets '((org-agenda-files . (:maxlevel . 6)))
org-capture-templates
'(("i" "Idea" entry (file+headline org-inbox-file "Inbox") "* %?\t\t:idea:\n")
("t" "Todo" entry (file+headline org-inbox-file "Inbox") "* TODO %?\n"))
org-log-into-drawer "LOGBOOK"
org-log-done "time"
org-clock-report-include-clocking-task t
org-clock-into-drawer t
org-fontify-done-headline t
org-enforce-todo-dependencies t
org-agenda-overriding-columns-format "%80ITEM(Task) %6Effort(Est){:} %6CLOCKSUM_T(Today) %6CLOCKSUM(Total)"
org-columns-default-format org-agenda-overriding-columns-format
org-agenda-columns-add-appointments-to-effort-sum t
org-use-property-inheritance t
org-confirm-babel-evaluate nil
org-id-link-to-org-use-id t
org-fold-catch-invisible-edits 'show
org-cycle-separator-lines 0
org-export-allow-bind-keywords t
;; let's try these out
org-use-speed-commands t
;; Pressing `l' in org-agenda-mode show the log of all state changes. Something I need because
;; otherwise old-scheduled and repeated tasks that I do, or tasks I cancel gets lost from my agenda
;; view
org-agenda-log-mode-items '(closed clock state))
;; org-mode settings
(with-eval-after-load 'org
(org-indent-mode t)
(require 'org-id))
#+end_src
- Keybindings
#+begin_src elisp
(global-set-key (kbd "C-c c") #'org-capture)
(my--defkeymap
"my-org" "C-c o"
'("a" . org-agenda-list)
'("A" . org-agenda)
'("c" . org-capture)
'("C" . org-clock-goto)
'("o" . consult-org-agenda))
(with-eval-after-load 'org
(define-key org-mode-map (kbd "C-c >") #'org-timestamp-inactive))
#+end_src
- org-super-agenda
:PROPERTIES:
:ID: 06dd246b-30f0-4c17-ab47-8128d49f7f69
:END:
More/better structure in agenda view.
#+begin_src elisp
(use-package org-super-agenda
:config
(org-super-agenda-mode t)
(setq org-super-agenda-groups
'((:name "In Progress" :todo "DOING" :order 1)
(:name "πͺ«" :tag "system" :order 1)
(:name "πΆπΌ" :category "Baby" :tag "baby" :order 2)
(:name "Work" :tag "work" :order 2)
(:name "π―" :category "Home π―" :tag "apartment" :order 3)
(:name "Projects" :tag "project" :order 3)
(:name "Self β€οΈ" :tag "health" :category "Self" :order 3)
(:name "Inbox" :tag "inbox" :order 4)
(:name "Study π" :tag ("study" "reading") :order 4)
(:name "Finance πΆ" :tag "finance" :order 4)
(:name "Habits" :tag "habit" :order 6))))
#+end_src
- org-babel
#+begin_src elisp
(with-eval-after-load 'org
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(plantuml . t)
(shell . t)
(sql . t)
(sqlite . t)
(lisp . t)
(js . t))))
#+end_src
- Allow adding HTML class/id to exported src blocks
Org mode don't allow adding custom HTML class or id to exported src
blocks, but I've found myself in need of this functionality when
customizing published projects.
#+begin_src elisp
(defun my--org-src-block-html-attrs-advice (oldfun src-block contents info)
"Add class, id or data-* CSS attributes to html source block output.
Allows class, id or data attributes to be added to a source block using
#attr_html:
,#+ATTR_HTML: :class myclass :id myid
,#+begin_src python
print(\"Hi\")
,#+end_src
"
(let* ((old-ret (funcall oldfun src-block contents info))
(class-tag (org-export-read-attribute :attr_html src-block :class))
(data-attr (let ((attr (org-export-read-attribute :attr_html src-block :data)))
(when attr (split-string attr "="))))
(id-tag (org-export-read-attribute :attr_html src-block :id)))
(if (or class-tag id-tag data-attr)
(concat
"
"
old-ret
"
")
old-ret)))
(advice-add 'org-html-src-block :around #'my--org-src-block-html-attrs-advice)
#+end_src
- Support exporting code blocks with syntax-highlighting
#+begin_src elisp
(use-package htmlize)
#+end_src
- Custom links
- =yt://= links
- Open =yt://= links in =mpv= if mpv is present
- Open =yt://= links in browser if mpv isn't installed or prefix-argument is
provided with =org-open-at-point= (i.e =C-c C-o=)
#+begin_src elisp
(defun my-org--follow-yt-link (path prefix)
(let* ((url (format "https:%s" path))
(proc-name (format "*yt://%s*" url)))
(if (and prefix (executable-find "mpv"))
(browse-url url)
(make-process :name proc-name :buffer proc-name :command `("mpv" ,url))
(message "Launched mpv in buffer: %s" proc-name))))
(defun my-org--export-yt-link (path desc backend)
(when (eq backend 'html)
(let* ((video-id (cadar (url-parse-query-string path)))
(url (if (string-empty-p video-id) path
(format "//youtube.com/embed/%s" video-id))))
(format
""
url desc))))
#+end_src
- Reset checkboxes when a subtree is repeated
Taken from [[https://emacs.stackexchange.com/a/70721][SO]]
#+begin_src elisp
(add-hook 'org-todo-repeat-hook #'org-reset-checkbox-state-subtree)
#+end_src
- Set tab-width for column mode
=org-columns= is complaining that tab-width in org-mode buffers should be 8 not 2.
#+begin_src elisp
(defun my--set-org-vars ()
(setf tab-width 8))
(add-hook 'org-agenda-mode-hook #'my--set-org-vars)
#+end_src
- Lib
Utility functions for working with org/denote that are used elsewhere in configuration.
#+begin_src elisp
(defun my--denote-files-with-tags (tags)
"Return denote files which have all TAGS."
(seq-filter (lambda (file)
(null (seq-difference tags (denote-extract-keywords-from-path file))))
(denote-directory-files)))
#+end_src
** Modal editing with Meow
:PROPERTIES:
:ID: 17c2eeec-133f-49f3-b2ce-95bf3dab1188
:END:
Let's get some modal editing with some spice. I have used Evil mode
with Spacemacs, I was going to configure Evil, but let's give meow a
shot!
- Meow qwerty setup copied from https://github.com/meow-edit/meow/blob/master/KEYBINDING_QWERTY.org
#+begin_src elisp
(defun meow-setup ()
(setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
(meow-motion-overwrite-define-key
'("j" . meow-next)
'("k" . meow-prev)
'("" . ignore))
(meow-leader-define-key
;; SPC j/k will run the original command in MOTION state.
'("j" . "H-j")
'("k" . "H-k")
;; Use SPC (0-9) for digit arguments.
'("1" . meow-digit-argument)
'("2" . meow-digit-argument)
'("3" . meow-digit-argument)
'("4" . meow-digit-argument)
'("5" . meow-digit-argument)
'("6" . meow-digit-argument)
'("7" . meow-digit-argument)
'("8" . meow-digit-argument)
'("9" . meow-digit-argument)
'("0" . meow-digit-argument)
;; '("/" . meow-keypad-describe-key)
'("?" . meow-cheatsheet))
(meow-normal-define-key
'("0" . meow-expand-0)
'("9" . meow-expand-9)
'("8" . meow-expand-8)
'("7" . meow-expand-7)
'("6" . meow-expand-6)
'("5" . meow-expand-5)
'("4" . meow-expand-4)
'("3" . meow-expand-3)
'("2" . meow-expand-2)
'("1" . meow-expand-1)
'("-" . negative-argument)
'(";" . meow-reverse)
'("," . meow-inner-of-thing)
'("." . meow-bounds-of-thing)
'("[" . meow-beginning-of-thing)
'("]" . meow-end-of-thing)
'("a" . meow-append)
'("A" . meow-open-below)
'("b" . meow-back-word)
'("B" . meow-back-symbol)
'("c" . meow-change)
'("d" . meow-delete)
'("D" . meow-backward-delete)
'("e" . meow-next-word)
'("E" . meow-next-symbol)
'("f" . meow-find)
'("g" . meow-cancel-selection)
'("G" . meow-grab)
'("h" . meow-left)
'("H" . meow-left-expand)
'("i" . meow-insert)
'("I" . meow-open-above)
'("j" . meow-next)
'("J" . meow-next-expand)
'("k" . meow-prev)
'("K" . meow-prev-expand)
'("l" . meow-right)
'("L" . meow-right-expand)
'("m" . meow-join)
'("n" . meow-search)
'("o" . meow-block)
'("O" . meow-to-block)
'("p" . meow-yank)
;; '("q" . meow-quit)
;; '("Q" . meow-goto-line)
'("r" . meow-replace)
'("R" . meow-swap-grab)
'("s" . meow-kill)
'("t" . meow-till)
'("u" . meow-undo)
'("U" . meow-undo-in-selection)
'("v" . meow-visit)
'("w" . meow-mark-word)
'("W" . meow-mark-symbol)
'("x" . meow-line)
;; '("X" . meow-goto-line)
'("y" . meow-save)
'("Y" . meow-sync-grab)
'("z" . meow-pop-selection)
'("'" . repeat)
'("" . ignore)))
#+end_src
#+begin_src elisp
(use-package meow
:config
(setf meow-use-clipboard t)
(meow-global-mode)
(meow-setup))
#+end_src
- Normal mode-keybindings. Mostly mimicking vim
#+begin_src elisp
(meow-normal-define-key
'("z" . my-fold)
'("/" . "C-s")
'("?" . "C-r"))
#+end_src
- Leader keybindings
#+begin_src elisp
(meow-leader-define-key
'("/" . consult-git-grep)
'("p" . "C-x p")
'("e" . flycheck-command-map)
'("w" . ace-window)
'("b" . my-buffers)
'("G" . my-git)
'("o" . my-org)
'("n" . my-notes))
#+end_src
- Keychords
#+begin_src elisp
(use-package key-chord
:config
(setf key-chord-two-keys-delay 0.1)
(key-chord-mode 1)
(key-chord-define meow-insert-state-keymap "fd" #'meow-insert-exit))
#+end_src
** Completion UI
:PROPERTIES:
:ID: 4b16f866-dede-4d72-8fbf-95044ed1e378
:END:
- Orderlies adds matches completion candidates by space-separated patterns in
any order
#+begin_src elisp
(use-package orderless
:config
(setq completion-styles '(orderless)))
#+end_src
- Vertico for completion UI
#+begin_src elisp
(use-package vertico)
(require 'vertico)
(with-eval-after-load 'vertico
(vertico-mode +1)
(define-key vertico-map (kbd "C-c ?") #'minibuffer-completion-help))
(use-package vertico-directory
:ensure nil
:straight nil
:after vertico
;; More convenient directory navigation commands
:bind (:map vertico-map
("C-h" . vertico-directory-delete-word))
;; Tidy shadowed file names
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
(use-package vertico-quick
:ensure nil
:straight nil
:after vertico
:bind (:map vertico-map
("C-q" . vertico-quick-insert))
;; Tidy shadowed file names
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
:ensure nil
:straight nil
:init
(savehist-mode +1))
;; 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)
#+end_src
- Marginalia adds pretty information to completions. It's pretty, useful, and
recommended by =embark= (it provides extra information to =embark=)
#+begin_src elisp
;; Enable richer annotations using the Marginalia package
(use-package marginalia)
(require 'marginalia)
(with-eval-after-load 'marginalia
(define-key minibuffer-local-map (kbd "M-A") #'marginalia-cycle)
(marginalia-mode +1))
#+end_src
- Consult for enhanced commands
#+begin_src elisp
(use-package consult
:config
(consult-customize consult-theme :preview-key '(:debounce 0.5 any))
(global-set-key (kbd "C-s") #'isearch-forward-regexp)
(global-set-key (kbd "C-S-s") #'consult-line)
(global-set-key (kbd "C-r") #'isearch-backward-regexp)
(global-set-key (kbd "C-S-r") #'isearch-backward-regexp)
(global-set-key (kbd "C-x b") #'consult-buffer)
(define-key my-buffers-keymap (kbd "b") #'consult-buffer)
(define-key my-buffers-keymap (kbd "B") #'consult-buffer-other-window)
;; better yank which show kill-ring for selection
(global-set-key (kbd "C-y") #'consult-yank-pop)
(meow-leader-define-key
'("/" . consult-ripgrep))
(meow-normal-define-key
'("p" . consult-yank-pop)
'("Q" . consult-goto-line)
'("X" . consult-focus-lines)))
(setq xref-show-xrefs-function #'consult-xref
xref-show-definitions-function #'consult-xref)
(recentf-mode +1)
(use-package consult-flycheck
:config
(define-key flycheck-command-map (kbd "l") #'consult-flycheck))
(use-package embark-consult
:after (embark consult)
:demand t
:hook
(embark-collect-mode . consult-preview-at-point-mode))
#+end_src
** Contextual actions
:PROPERTIES:
:ID: 8AF4F0B4-6688-439D-87D8-A70FD320B547
:END:
- [[https://github.com/oantolin/embark][embark]] allow contextual actions, like opening buffers in other window from
minibuffer and a lot more
#+begin_src elisp
(defun my--embark-act-no-quit ()
"(embark-act), but don't quit the minibuffer"
(interactive)
(let ((embark-quit-after-action nil))
(embark-act)))
(use-package embark
:bind
(("C-," . embark-act)
("C->" . embark-act-all)
("C-." . embark-dwim)
("C-h b" . embark-bindings)
("C-<" . my--embark-act-no-quit)))
#+end_src
** More powerful editing
:PROPERTIES:
:ID: 968A1471-8B1D-4DD9-B871-E5B44EE2EF69
:END:
- =wgrep= for editing grep buffers
#+begin_src elisp
(use-package wgrep)
#+end_src
- =undo-tree-mode= for more powerful undo
#+begin_src elisp
(use-package undo-tree
:config
(global-undo-tree-mode t)
(global-set-key (kbd "C-/") #'undo)
(global-set-key (kbd "C-S-/") #'undo-tree-redo)
(setq undo-tree-history-directory-alist `(("." . ,(expand-file-name ".cache" user-emacs-directory)))))
#+end_src
- =embrace= for wrapping pair manipulation
#+begin_src elisp
(let ((embrace-dir (expand-file-name "~/code/emacs/embrace.el")))
(when (file-exists-p embrace-dir)
(add-to-list 'load-path embrace-dir)
(require 'embrace)
(with-eval-after-load 'embrace
(add-hook 'org-mode-hook 'embrace-org-mode-hook))
(meow-normal-define-key
'("S" . embrace-commander))))
#+end_src
- [[https://github.com/joaotavora/yasnippet][yasnippet]] for templates
#+begin_src emacs-lisp
(require 'yasnippet)
(with-eval-after-load 'yasnippet (add-hook 'prog-mode-hook #'yas-minor-mode))
(use-package yasnippet-snippets
:after yasnippet)
#+end_src
#+RESULTS:
** Programming
:PROPERTIES:
:ID: f88fd5b1-1170-43e3-b2b9-e3060edd7442
:END:
- Show trailing whitespace in programming files
#+begin_src elisp
(add-hook 'prog-mode-hook #'(lambda () (setq-local show-trailing-whitespace t)))
#+end_src
- Wrapping text in parens, quotes etc
#+begin_src elisp
(show-paren-mode 1)
(electric-pair-mode 1)
#+end_src
- Code folding
#+begin_src elisp
(my--defkeymap
"my-fold" "C-c f"
'("b" . hs-hide-block)
'("O" . hs-show-block)
'("l" . hs-hide-level)
'("L" . hs-show-block)
'("a" . hs-hide-all)
'("A" . hs-show-all)
'("z" . hs-toggle-hiding))
(add-hook 'prog-mode-hook 'hs-minor-mode)
#+end_src
- Flycheck for getting those in-buffer warnings errors.
#+begin_src elisp
(use-package flycheck
:init
(global-flycheck-mode t)
;; alias is needed for using the keymap in meow
(defalias 'flycheck-command-map flycheck-command-map))
#+end_src
Flycheck eglot integration
#+begin_src elisp
(straight-use-package 'flycheck-eglot)
(with-eval-after-load 'flycheck-eglot
(global-flycheck-eglot-mode 1))
#+end_src
- Completions
I think I have a general idea of what it does, but still fuzzy on
details. This stuff is usually taken for granted; I've been taking
it for granted with Spacemacs for a while now I suppose.
#+begin_src elisp
(straight-use-package
'(nerd-icons-corfu
:host github
:repo "LuigiPiucco/nerd-icons-corfu"))
(use-package corfu
:config
(setf corfu-auto t)
(global-corfu-mode t)
(corfu-popupinfo-mode t)
(add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))
#+end_src
- [[https://github.com/purcell/emacs-reformatter][Reformatter]] allow creating buffer/region formatters from any command.
#+begin_src elisp
(use-package reformatter
:config
(reformatter-define prettier-format
:program (expand-file-name "node_modules/.bin/prettier"
(locate-dominating-file (buffer-file-name) "node_modules/.bin/prettier"))
:args `("--stdin-filepath" ,(buffer-file-name)))
:hook ((web-mode . prettier-format-on-save-mode)
(typescript-ts-mode . prettier-format-on-save-mode)))
#+end_src
- Direnv is pretty essential for my dev workflow.
#+begin_src elisp
(use-package direnv
:config
(direnv-mode)
(when (not (boundp 'warning-suppress-types))
(setq warning-suppress-types nil))
(add-to-list 'warning-suppress-types '(direnv)))
#+end_src
- Eglot to provide LSP support.
#+begin_src elisp
;; Need to be manuall installed so we get latest version
;; (use-package jsonrpc
;; :ensure '(jsonrpc :repo "https://git.savannah.gnu.org/git/emacs.git"
;; :files ("lisp/jsonrpc.el")))
;; (use-package eldoc
;; :ensure '(eldoc :repo "https://git.savannah.gnu.org/git/emacs.git"
;; :files ("lisp/emacs-lisp/eldoc.el")))
(use-package eglot
:straight nil
:ensure nil)
;; Looks like jsonrpc logging make eglot super laggy for typescript.
;; https://old.reddit.com/r/emacs/comments/1447fy2/looking_for_help_in_improving_typescript_eglot/
;; https://www.reddit.com/r/emacs/comments/16vixg6/how_to_make_lsp_and_eglot_way_faster_like_neovim/
(fset #'jsonrpc--log-event #'ignore)
(setq eglot-events-buffer-size 0
eglot-sync-connect nil
eglot-connect-timeout nil
company-idle-delay 0
company-minimum-prefix-length 1)
(add-hook 'focus-out-hook 'garbage-collect)
#+end_src
*** Treesitter
:PROPERTIES:
:ID: 780140a8-6b04-414b-8e49-bc8c9fbe9b17
:END:
#+begin_src elisp
(setq treesit-language-source-alist
'((bash "https://github.com/tree-sitter/tree-sitter-bash")
(cmake "https://github.com/uyha/tree-sitter-cmake")
(css "https://github.com/tree-sitter/tree-sitter-css")
(elisp "https://github.com/Wilfred/tree-sitter-elisp")
(go "https://github.com/tree-sitter/tree-sitter-go")
(html "https://github.com/tree-sitter/tree-sitter-html")
(javascript "https://github.com/tree-sitter/tree-sitter-javascript" "master" "src")
(json "https://github.com/tree-sitter/tree-sitter-json")
(make "https://github.com/alemuller/tree-sitter-make")
(markdown "https://github.com/ikatyang/tree-sitter-markdown")
(python "https://github.com/tree-sitter/tree-sitter-python")
(toml "https://github.com/tree-sitter/tree-sitter-toml")
(tsx "https://github.com/tree-sitter/tree-sitter-typescript" "master" "tsx/src")
(typescript "https://github.com/tree-sitter/tree-sitter-typescript" "master" "typescript/src")
(yaml "https://github.com/ikatyang/tree-sitter-yaml")))
#+end_src
- Structured movement
#+begin_src elisp
(use-package combobulate
:custom
;; You can customize Combobulate's key prefix here.
;; Note that you may have to restart Emacs for this to take effect!
(setf combobulate-key-prefix "C-c j")
;; Optional, but recommended.
;;
;; You can manually enable Combobulate with `M-x
;; combobulate-mode'.
:hook
((python-ts-mode . combobulate-mode)
(js-ts-mode . combobulate-mode)
(html-ts-mode . combobulate-mode)
(css-ts-mode . combobulate-mode)
(yaml-ts-mode . combobulate-mode)
(typescript-ts-mode . combobulate-mode)
(json-ts-mode . combobulate-mode)
(tsx-ts-mode . combobulate-mode)))
#+end_src
*** Lisp
:PROPERTIES:
:ID: 828dd6e7-a386-415c-b4e1-cb5515138109
:END:
Lispy for some nasty lisp structural editing.
#+begin_src elisp
(use-package lispy
:hook ((emacs-lisp-mode . lispy-mode)
(lisp-mode . lispy-mode)
(scheme-mode . lispy-mode))
:config
(setf lispy-colon-p nil))
#+end_src
Pretty cool auto-indentation.
#+begin_src elisp
(use-package aggressive-indent
:hook ((emacs-lisp-mode . aggressive-indent-mode)
(lisp-mode . aggressive-indent-mode)))
#+end_src
- Common Lisp
Sly for interactive development.
#+begin_src elisp
(use-package sly
:hook ((lisp-mode . sly-mode))
:config
(setq org-babel-lisp-eval-fn #'sly-eval
inferior-lisp-program "sbcl")
(add-hook
'sly-mrepl-hook
(lambda () (set-face-foreground 'sly-mrepl-output-face "khaki3"))))
#+end_src
[[https://github.com/mmgeorge/sly-asdf][sly-asdf]] add asdf integration to sly.
#+begin_src elisp
(use-package sly-asdf
:config
(add-to-list 'sly-contribs 'sly-asdf 'append))
#+end_src
*** Web dev
:PROPERTIES:
:ID: 62e08f15-d996-48fd-90c3-fd6d348555be
:END:
- Helper utilities
- Are we using nvm?
#+begin_src elisp
(defun my--nvm-p ()
(when-let* ((node (string-trim (shell-command-to-string "fish -c 'readlink (which node)'")))
(nvm-bin-dir
(and (string-match-p "\/nvm\/" node)
(file-name-directory node))))
nvm-bin-dir))
#+end_src
#+begin_src elisp
(setq css-indent-offset my--indent-width)
(use-package js-ts
:mode "\\.js'"
:straight nil
:ensure nil
:config
(setq js-indent-level my--indent-width)
:hook
(((js-ts-mode
typescript-ts-mode) . subword-mode)))
(use-package web-mode
:mode (("\\.html?\\'" . web-mode))
:config
(setq web-mode-markup-indent-offset my--indent-width)
(setq web-mode-code-indent-offset my--indent-width)
(setq web-mode-css-indent-offset my--indent-width)
(setq web-mode-content-types-alist '(("jsx" . "\\.js[x]?\\'"))))
(use-package emmet-mode
:hook ((html-mode . emmet-mode)
(css-mode . emmet-mode)
(js-ts-mode . emmet-mode)
(js-jsx-mode . emmet-mode)
(typescript-ts-mode . emmet-mode)
(tsx-ts-mode . emmet-mode)
(web-mode . emmet-mode))
:config
(setq emmet-insert-flash-time 0.001) ; effectively disabling it
(add-hook 'js-jsx-mode-hook #'(lambda ()
(setq-local emmet-expand-jsx-className? t)))
(add-hook 'web-mode-hook #'(lambda ()
(setq-local emmet-expand-jsx-className? t))))
(my--defkeymap
"my-errors" "C-c e"
'("n" . flycheck-next-error)
'("p" . flycheck-previous-error)
'("l" . flycheck-list-errors)
'("e" . flycheck-explain-error-at-point))
(defun my--setup-ts-js ()
"Setup Javascript and Typescript for current buffer."
;; Add node_modules/.bin of current project to exec-path.
(if-let (nvm-bin (my--nvm-p))
(add-to-list 'exec-path nvm-bin)
(let ((bin-dir
(expand-file-name
"node_modules/.bin/"
(locate-dominating-file default-directory "node_modules"))))
(when (file-exists-p bin-dir)
(add-to-list 'exec-path bin-dir))))
(subword-mode)
;; For 95% of cases this is what I want
(prettier-format-on-save-mode)
(setf flymake-eslint-project-root
(locate-dominating-file default-directory "package.json"))
(eglot-ensure))
(add-hook 'js-ts-mode-hook #'my--setup-ts-js)
(use-package typescript-ts-mode
:mode "\\.ts\\'"
:config
(setq-default typescript-indent-level my--indent-width)
(add-hook 'typescript-mode-hook #'my--setup-ts-js)
(add-hook 'typescriptt-ts-mode-hook #'my--setup-ts-js))
(use-package tsx-ts-mode
:mode "\\.tsx\\'"
:straight nil
:ensure nil
:hook ((tsx-ts-mode . subword-mode)
(tsx-ts-mode . my--setup-ts-js))
:config
(setq typescript-indent-level my--indent-width))
(use-package css-ts-mode
:ensure nil
:straight nil
:mode "\\.s?css\\'")
#+end_src
- JSON support
#+begin_src elisp
(use-package json-ts-mode
:mode "\\.json\\'")
#+end_src
*** Rust
:PROPERTIES:
:ID: 9A781996-6C6F-439D-B75D-E9F05BAD99F0
:END:
#+begin_src elisp
;; (use-package rustic
;; :init
;; (setq rustic-cargo-bin "cargo")
;; (push 'rustic-clippy flycheck-checkers))
#+end_src
*** Haskell
:PROPERTIES:
:ID: 3720944C-C928-4ECF-9B35-7620EEF7C682
:END:
#+begin_src elisp
(use-package haskell-mode
:mode "\\.hs\\'"
:config
(add-hook 'haskell-mode-hook #'subword-mode)
(define-key haskell-mode-map (kbd "C-c , c") #'haskell-process-load-or-reload)
(define-key haskell-mode-map (kbd "C-c , s") #'haskell-interactive-switch)
(define-key haskell-mode-map (kbd "C-c , l") #'haskell-interactive-mode-clear)
(define-key haskell-mode-map (kbd "C-c , T") #'haskell-doc-show-type)
(define-key haskell-mode-map (kbd "C-c , t") #'haskell-mode-show-type-at))
#+end_src
*** Yaml
:PROPERTIES:
:ID: C204CCCB-D27E-48F2-BEBB-A9A913C763A4
:END:
#+begin_src elisp
(use-package yaml-mode
:mode "\\.ya?ml\\'")
#+end_src
*** Graphql
:PROPERTIES:
:ID: 204E2F4A-9F0A-4458-8135-DD3861052AE3
:END:
#+begin_src elisp
(use-package graphql-mode
:mode "\\.graphql\\'")
#+end_src
** Niceties
:PROPERTIES:
:ID: 8e8563f8-2161-4af3-b072-fc3b81cc57a6
:END:
Nice to have features but not necessary.
- Ace Jump for quickly jumping around in a buffer
#+begin_src elisp
(my--defkeymap
"my-jump" "C-c q"
'("q" . ace-jump-mode)
'("w" . ace-jump-word-mode))
(use-package ace-jump-mode)
#+end_src
- Treemacs for easy code exploration
#+begin_src elisp
(use-package treemacs
:defer t
:init
(with-eval-after-load 'winum
(define-key winum-keymap (kbd "M-0") #'treemacs-select-window))
:config
(progn
(setq treemacs-collapse-dirs (if treemacs-python-executable 3 0)
treemacs-deferred-git-apply-delay 0.5
treemacs-directory-name-transformer #'identity
treemacs-display-in-side-window t
treemacs-eldoc-display 'simple
treemacs-file-event-delay 2000
treemacs-file-extension-regex treemacs-last-period-regex-value
treemacs-file-follow-delay 0.2
treemacs-file-name-transformer #'identity
treemacs-follow-after-init t
treemacs-expand-after-init t
treemacs-find-workspace-method 'find-for-file-or-pick-first
treemacs-git-command-pipe ""
treemacs-goto-tag-strategy 'refetch-index
treemacs-header-scroll-indicators '(nil . "^^^^^^")
treemacs-hide-dot-git-directory t
treemacs-indentation 2
treemacs-indentation-string " "
treemacs-is-never-other-window nil
treemacs-max-git-entries 5000
treemacs-missing-project-action 'ask
treemacs-move-forward-on-expand nil
treemacs-no-png-images nil
treemacs-no-delete-other-windows t
treemacs-project-follow-cleanup nil
treemacs-persist-file (expand-file-name ".cache/treemacs-persist" user-emacs-directory)
treemacs-position 'left
treemacs-read-string-input 'from-child-frame
treemacs-recenter-distance 0.1
treemacs-recenter-after-file-follow nil
treemacs-recenter-after-tag-follow nil
treemacs-recenter-after-project-jump 'always
treemacs-recenter-after-project-expand 'on-distance
treemacs-litter-directories '("/node_modules" "/.venv" "/.cask")
treemacs-show-cursor nil
treemacs-show-hidden-files t
treemacs-silent-filewatch nil
treemacs-silent-refresh nil
treemacs-sorting 'alphabetic-asc
treemacs-select-when-already-in-treemacs 'move-back
treemacs-space-between-root-nodes t
treemacs-tag-follow-cleanup t
treemacs-tag-follow-delay 1.5
treemacs-text-scale nil
treemacs-user-mode-line-format nil
treemacs-user-header-line-format nil
treemacs-wide-toggle-width 70
treemacs-width 35
treemacs-width-increment 1
treemacs-width-is-initially-locked t
treemacs-workspace-switch-cleanup nil)
;; The default width and height of the icons is 22 pixels. If you are
;; using a Hi-DPI display, uncomment this to double the icon size.
;;(treemacs-resize-icons 44)
(treemacs-follow-mode t)
(treemacs-filewatch-mode t)
(treemacs-fringe-indicator-mode 'always)
;; (when treemacs-python-executable
;; (treemacs-git-commit-diff-mode t))
(pcase (cons (not (null (executable-find "git")))
(not (null treemacs-python-executable)))
(`(t . t)
(treemacs-git-mode 'deferred))
(`(t . _)
(treemacs-git-mode 'simple)))
(treemacs-hide-gitignored-files-mode nil))
:bind
(:map global-map
("M-0" . treemacs-select-window)
("C-x t 1" . treemacs-delete-other-windows)
("C-x t t" . treemacs)
("C-x t d" . treemacs-select-directory)
("C-x t B" . treemacs-bookmark)
("C-x t C-t" . treemacs-find-file)
("C-x t M-t" . treemacs-find-tag)))
(use-package treemacs-magit
:after (treemacs magit))
(use-package treemacs-all-the-icons
:config
(treemacs-load-theme "all-the-icons"))
#+end_src
- Highlight indentation
#+begin_src elisp
(use-package highlight-indent-guides
:config
(setf highlight-indent-guides-method 'bitmap)
(add-hook 'prog-mode-hook 'highlight-indent-guides-mode))
#+end_src
- Move text around
#+begin_src elisp
(use-package move-text
:config
(move-text-default-bindings))
#+end_src
** Looks
:PROPERTIES:
:ID: baaa3b17-3676-4759-b2a0-dc792897862b
:END:
#+begin_src elisp
;; (use-package doom-themes
;; :config
;; (setq doom-rouge-brighter-modeline t
;; doom-rouge-brighter-comments t)
;; (load-theme 'doom-rouge t))
;; (use-package nimbus-theme
;; :config
;; (load-theme 'nimbus t))
(use-package modus-themes
:config
(load-theme 'modus-vivendi-tinted :no-confirm))
#+end_src
Modeline
#+begin_src elisp
;; (use-package doom-modeline
;; :config
;; (setq doom-modeline-height 24)
;; (doom-modeline-mode 1))
#+end_src
Let's also try smooth-scrolling.
#+begin_src elisp
(pixel-scroll-precision-mode t)
#+end_src
** Applications
:PROPERTIES:
:ID: 9061cb70-e3e7-49d5-8fec-476f36ea3d47
:END:
Non crucial things which should be loaded last. If they fail, nothing crucial is
blocked.
#+begin_src elisp
(my--defkeymap "my-apps" "C-c a")
#+end_src
- Spell checking
#+begin_src elisp
(with-eval-after-load "ispell"
(setq ispell-program-name "hunspell")
(setq ispell-dictionary "en_US,de_DE")
(ispell-set-spellchecker-params)
(ispell-hunspell-add-multi-dic "en_US,de_DE")
(setq ispell-personal-dictionary "~/.emacs.d/.hunspell_per_dic"))
#+end_src
#+begin_src elisp
(use-package flyspell
:hook
(text-mode . flyspell-mode)
(prog-mode . flyspell-prog-mode)
:config
(define-key flyspell-mode-map (kbd "C-,") nil)
(define-key flyspell-mode-map (kbd "C-.") nil)
(define-key flyspell-mode-map (kbd "C-;") #'flyspell-correct-wrapper))
(use-package flyspell-correct
:after (flyspell)
:commands (flyspell-correct-at-point
flyspell-correct-wrapper))
#+end_src
- Notes using denotes
#+begin_src elisp
(setq denote-directory (expand-file-name "denotes" org-directory)
denote-date-prompt-use-org-read-date t)
(use-package denote
:config
(add-hook 'dired-mode-hook #'denote-dired-mode))
#+end_src
- Enhance denote a bit, don't know why these aren't a part of denote itself.
#+begin_src elisp
(defun my--denote-split-org-subtree (&optional prefix)
"Create new Denote note as an Org file using current Org subtree."
(interactive "P")
(let ((text (org-get-entry))
(heading (org-get-heading :no-tags :no-todo :no-priority :no-comment))
(tags (org-get-tags))
(subdir (when prefix (denote-subdirectory-prompt))))
(delete-region (org-entry-beginning-position) (org-entry-end-position))
(denote heading tags 'org subdir)
(insert text)))
#+end_src
- Setup for taking notes for reading/video-watching I do in Firefox.
/Moved to denote/
- CRM
#+begin_src elisp
(defvar crm-directory (expand-file-name "crm" denote-directory))
(defun my-crm--open-or-create ()
"Find or create CRM entry."
(interactive)
(let ((denote-directory crm-directory))
(call-interactively #'denote-open-or-create)))
(defun my-crm--link-or-create ()
"Find or create CRM entry."
(interactive)
(let ((denote-directory crm-directory))
(call-interactively #'denote-link-or-create)))
#+end_src
- Keyboard shortcuts for fluent note-taking/reading
#+begin_src elisp
(my--defkeymap
"my-notes" "C-c n"
'("n" . denote-open-or-create)
'("N" . denote-link-or-create)
'("b" . denote-backlinks)
'("d" . my--diary-today)
'("p" . my-crm--open-or-create)
'("P" . my-crm--link-or-create)
'("m" . my--micro-post))
#+end_src
- Diary
#+begin_src elisp
(defun my--find-habit (title)
"Find the habit with TITLE in current buffer."
(cl-block 'my--find-habit
(org-map-entries
(lambda ()
(let ((el (org-element-at-point-no-context)))
(when (and (seq-contains-p (org-get-tags el) "habit" #'equal)
(equal (downcase (org-element-property :raw-value el))
(downcase title)))
(cl-return-from 'my--find-habit el)))))))
(defun my--mark-habit-as-done (habit file)
"Mark HABIT as done."
(with-current-buffer (find-file-noselect file)
(org-mode)
(let ((el (cl-case habit
(diary (my--find-habit "write diary entry")))))
(goto-char (org-element-property :begin el))
(org-todo 'done))))
(defun my--diary-today ()
"Go to today's diary entry."
(interactive)
(let* ((diary-habit-file (denote-get-path-by-id "20241231T055317"))
(denote-directory (expand-file-name "diary" denote-directory))
(title (format-time-string "%Y-%m-%d")))
(if-let ((file (seq-find
(lambda (f) (string-match-p title f))
(directory-files denote-directory))))
(progn
(find-file (expand-file-name file denote-directory))
(goto-char (point-max)))
(my--mark-habit-as-done 'diary diary-habit-file)
(denote title '("diary")))))
#+end_src
- Work notes
#+begin_src elisp
(defun my--workday-notes (prefix)
"Go to work notes for today plus PREFIX days."
(interactive "P")
(let* ((days (if prefix (prefix-numeric-value prefix) 0))
(denote-directory (expand-file-name "work" denote-directory))
(date (time-add (current-time) (days-to-time days)))
(title (format-time-string "%Y-%m-%d" date)))
(if-let ((file (seq-find
(lambda (f) (string-match-p title f))
(directory-files denote-directory)))
(file (expand-file-name file denote-directory)))
(progn
(find-file file)
;; Remove any other denotes/work file from agenda
;; Assuming that this will always remove older workday files
(setf org-agenda-files
(seq-filter
(lambda (file)
(not (string-match-p "denotes/work" file)))
org-agenda-files))
(org-agenda-file-to-front file)
(goto-char (point-max)))
(denote title '("work") "org" nil title))))
(my--defkeymap
"workday" "C-c n w"
'("w" . my--workday-notes)
'("i" . on-issue-note-open-or-create)
'("I" . on-issue-note-link-or-create))
#+end_src
- dirvish for more powerful dired
#+begin_src elisp
(use-package all-the-icons)
(use-package dirvish)
(with-eval-after-load 'dirvish
(dirvish-override-dired-mode)
;; (setq dirvish-attributes
;; '(vc-state subtree-state all-the-icons collapse file-size))
(global-set-key (kbd "C-c f") #'dirvish-fd)
(define-key dirvish-mode-map (kbd "/") #'dirvish-narrow)
(define-key dirvish-mode-map (kbd "a") #'dirvish-quick-access)
(define-key dirvish-mode-map (kbd "f") #'dirvish-file-info-menu)
(define-key dirvish-mode-map (kbd "y") #'dirvish-yank-menu)
(define-key dirvish-mode-map (kbd "N") #'dirvish-narrow)
(define-key dirvish-mode-map (kbd "^") #'dirvish-history-last)
(define-key dirvish-mode-map (kbd "h") #'dirvish-history-jump) ; remapped `describe-mode'
(define-key dirvish-mode-map (kbd "s") #'dirvish-quicksort) ; remapped `dired-sort-toggle-or-edit'
;; (define-key dirvish-mode-map (kbd "v") #'dirvish-vc-menu) ; remapped `dired-view-file'
(define-key dirvish-mode-map (kbd "TAB") #'dirvish-subtree-toggle)
(define-key dirvish-mode-map (kbd "M-f") #'dirvish-history-go-forward)
(define-key dirvish-mode-map (kbd "M-b") #'dirvish-history-go-backward)
(define-key dirvish-mode-map (kbd "M-l") #'dirvish-ls-switches-menu)
(define-key dirvish-mode-map (kbd "M-m") #'dirvish-mark-menu)
(define-key dirvish-mode-map (kbd "M-t") #'dirvish-layout-toggle)
(define-key dirvish-mode-map (kbd "M-s") #'dirvish-setup-menu)
(define-key dirvish-mode-map (kbd "M-e") #'dirvish-emerge-menu)
(define-key dirvish-mode-map (kbd "M-j") #'dirvish-fd-jump))
#+end_src
- Ledger
#+begin_src elisp
(use-package ledger-mode
:mode "\\.ledger\\'"
:config
(setq ledger-default-date-format ledger-iso-date-format))
#+end_src
- spookfox
#+begin_src elisp
(setq spookfox-dir (expand-file-name "~/code/spookfox"))
(when (file-exists-p spookfox-dir)
(add-to-list 'load-path (expand-file-name "lisp" spookfox-dir))
(add-to-list 'load-path (expand-file-name "lisp/apps" spookfox-dir))
;; Manually load spookfox dependencies
(use-package websocket)
(require 'spookfox)
(require 'spookfox-tabs)
(require 'spookfox-windows)
(require 'spookfox-js-injection)
(eval-after-load 'spookfox
(spookfox-start-server))
(defun my--switch-tab-and-focus ()
"Switch to browser tab and bring browser in focus."
(interactive)
(spookfox-switch-tab)
(when (eq 'darwin system-type)
(ns-do-applescript "tell application \"Firefox\"\n\tactivate\n\tend tell")))
(define-key my-buffers-keymap (kbd "t") #'my--switch-tab-and-focus))
#+end_src
- saunf
Use the local repo; very risky, should change.
#+begin_src elisp
(let ((saunf-dir (expand-file-name "~/code/saunf/src")))
(when (file-exists-p saunf-dir)
(add-to-list 'load-path saunf-dir)
(require 'saunf)))
#+end_src
- org-noter
#+begin_src elisp
(use-package nov
:mode ("\\.epub\\'" . nov-mode)
:config
(setq nov-shr-rendering-functions '((img . nov-render-img) (title . nov-render-title)))
(setq nov-shr-rendering-functions (append nov-shr-rendering-functions shr-external-rendering-functions)))
(when (assoc 'powered-by-guix my--feature-flags)
(require 'org-pdftools)
(require 'pdf-tools)
(require 'org-noter))
#+end_src
- eshell
I sometimes open eshell buffers. Following kbd actually clears the eshell buffer, which is
something I like to do in my terminals.
#+begin_src elisp
(require 'esh-mode)
(with-eval-after-load 'esh-mode
(define-key eshell-mode-map (kbd "C-c M-o")
(lambda () (interactive)
(eshell/clear-scrollback)
(eshell-send-input
))))
#+end_src
- Irc
Small utility to quickly connect to irc.
#+begin_src elisp
(use-package erc
:config
(setopt erc-modules (seq-union '(sasl) erc-modules))
(setq
erc-nick "bitspook"
erc-auth-source-server-function (lambda (a b)
(auth-source-pass-get 'secret "libera.chat/bitspook"))
erc-sasl-user :nick
erc-autojoin-channels-alist '(("libera.chat"
"#commonlisp"
"#emacs"
"#emacs-berlin"
"#clschool"
"#whereiseveryone"
"#guix"
"#lisp"
;; just explorin'
"#berlin"
"##deutsch")))
(setf erc-track-exclude-types '("JOIN" "KICK" "NICK" "PART" "QUIT" "MODE" "333" "353")))
(use-package erc-track
:ensure nil
:straight nil
;; Prevent JOINs and PARTs from lighting up the mode-line.
:config (setopt erc-track-faces-priority-list
(remq 'erc-notice-face erc-track-faces-priority-list))
:custom (erc-track-priority-faces-only 'all))
#+end_src
- Terraform
#+begin_src elisp
(use-package terraform-mode
:config
(add-hook 'terraform-mode-hook #'terraform-format-on-save-mode))
#+end_src
- Eww
#+begin_src elisp
(setf eww-readable-urls '(".*"))
(add-hook
'eww-mode-hook
(lambda ()
(display-line-numbers-mode -1)))
#+end_src
Syntax-highlighting for code blocks in HTML.
#+begin_src elisp
(use-package shr-tag-pre-highlight
:straight t
:after shr
:config
(add-to-list 'shr-external-rendering-functions
'(pre . shr-tag-pre-highlight)))
#+end_src
Make EWW buffer niceer with org-outline like features.
#+begin_src elisp
(use-package shrface
:straight t
:config
(shrface-basic)
(shrface-trial)
(shrface-default-keybindings) ; setup default keybindings
(setq shrface-href-versatile t)
;; configure eww
(add-hook 'eww-after-render-hook #'shrface-mode))
#+end_src
- org-download to easily attach images in my notes
#+begin_src elisp
(use-package org-download
:init
(setq-default org-download-method 'attach
org-download-image-dir (expand-file-name "data" org-directory))
:config
;; Drag-and-drop to `dired`
(add-hook 'dired-mode-hook 'org-download-enable))
#+end_src
- Ement
#+begin_src elisp
(use-package ement)
(define-key
my-apps-keymap
(kbd "e") (define-keymap
:parent my-apps-keymap
:name "Ement"
(kbd "e") #'ement-room-list
(kbd "n") #'ement-notifications))
#+end_src
** System management
:PROPERTIES:
:ID: e951d611-5827-41d7-b4b4-5f2b1bfcfbef
:END:
- Libretranslate for translating Deutsch from Emacs
#+begin_src elisp
(straight-use-package '(libretrans :host codeberg :repo "martianh/libretrans.el"))
(setq libretrans-instance "http://red:5005/translate"
libretrans-source "de"
libretrans-target "en")
#+end_src
** Private work related config
:PROPERTIES:
:ID: 6CA859BF-001A-48A8-8FAF-A522EE7FC8B1
:END:
#+begin_src elisp
(let ((private-config (expand-file-name "./private.el" user-emacs-directory)))
(when (file-exists-p private-config)
(load-file private-config)))
#+end_src
** My workflows
:PROPERTIES:
:ID: 5acc224e-0d3c-4962-8b47-10e319286b9b
:END:
I am experimenting with putting my configuration in blog posts (called "workflows"). These posts
are the source of truth for the configuration, which is tangled to =~/.emacs.d/workflow-*.el=
files. Let's load all of 'em.
#+begin_src elisp
(let ((root-workflow (expand-file-name "init-workflows.el" user-emacs-directory)))
(when (file-exists-p root-workflow)
(load-file root-workflow)))
#+end_src