#+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