#+PROPERTY: header-args :tangle ./init.el #+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. Elpaca will now clone all the packages, and then you'll be ready for a spooky 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: My [[https://github.com/progfolio/elpaca][Elpaca]] based emacs configuration. - Bootstrap #+begin_src elisp :tangle ./early-init.el ;; -*- lexical-binding: t -*- (setq package-enable-at-startup nil package-install-upgrade-built-in 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) #+end_src ** Initialize Elpaca itself :PROPERTIES: :ID: 635cd7c4-e3cb-4a0e-a722-6fa8f2035ea0 :END: #+begin_src elisp :tangle ./init.el ;; -*- lexical-binding: t -*- (defvar elpaca-installer-version 0.7) (defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory)) (defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory)) (defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory)) (defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git" :ref nil :files (:defaults (:exclude "extensions")) :build (:not elpaca--activate-package))) (let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory)) (build (expand-file-name "elpaca/" elpaca-builds-directory)) (order (cdr elpaca-order)) (default-directory repo)) (add-to-list 'load-path (if (file-exists-p build) build repo)) (unless (file-exists-p repo) (make-directory repo t) (when (< emacs-major-version 28) (require 'subr-x)) (condition-case-unless-debug err (if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*")) ((zerop (call-process "git" nil buffer t "clone" (plist-get order :repo) repo))) ((zerop (call-process "git" nil buffer t "checkout" (or (plist-get order :ref) "--")))) (emacs (concat invocation-directory invocation-name)) ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch" "--eval" "(byte-recompile-directory \".\" 0 'force)"))) ((require 'elpaca)) ((elpaca-generate-autoloads "elpaca" repo))) (progn (message "%s" (buffer-string)) (kill-buffer buffer)) (error "%s" (with-current-buffer buffer (buffer-string)))) ((error) (warn "%s" err) (delete-directory repo 'recursive)))) (unless (require 'elpaca-autoloads nil t) (require 'elpaca) (elpaca-generate-autoloads "elpaca" repo) (load "./elpaca-autoloads"))) (add-hook 'after-init-hook #'elpaca-process-queues) (elpaca `(,@elpaca-order)) #+end_src - Install =use-package= #+begin_src elisp ;; Install use-package support (elpaca elpaca-use-package ;; Enable :elpaca use-package keyword. (elpaca-use-package-mode) ;; Assume :elpaca t unless otherwise specified. (setq elpaca-use-package-by-default t)) (elpaca-wait) #+end_src ** Helper utilities :PROPERTIES: :ID: 675D81B1-9A5A-44E8-BA29-888C967974F9 :END: - Ergonomically create keymaps #+begin_src elisp (defmacro spook--defkeymap (name prefix &rest bindings) "Create a new NAME-keymap bound to PREFIX, with BINDINGS. Usage: (spook--defkeymap \"spook-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 spook--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 spook--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) (spook--get-or-create-scratch))) #+end_src - Profiles Let's introduce a concept of profiles 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, we'll create a default configuration, and modify it based on which profiles are active right now. At startup, we'll perform the checks to automatically enable certain profiles. A profile is a cons cell of =(name . metadata)= #+begin_src elisp (defvar spook--active-profiles '() "Change things slightly based on different profiles.") #+end_src - On mac without external monitor #+begin_src elisp (when (eq system-type 'darwin) (push '(small-screen . t) spook--active-profiles)) #+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 spook--indent-width 2) (setq-default tab-width spook--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 100) #+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 - Window management - Custom window keybindings #+begin_src elisp (spook--defkeymap "spook-windows" "C-c s-w" '("-" . split-window-below) '("_" . spook--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 spook--aw-kill-buffer-in-window (win) "Kill the buffer shown in window WIN." (kill-buffer (window-buffer win))) (defun spook--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 spook--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 spook--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 - Workspace management with perspective I was using eyebrowse earlier, but I don't like its reliance on desktop-mode to save state. Let's give perspective a shot #+begin_src elisp (use-package perspective :init (setq persp-mode-prefix-key (kbd "C-c C-w")) :config (persp-mode +1)) #+end_src - Buffer management #+begin_src elisp (spook--defkeymap "spook-buffers" "C-c b" '("b" . switch-to-buffer) '("n" . next-buffer) '("p" . previous-buffer) '("n" . next-buffer) '("d" . kill-current-buffer) '("s" . spook--get-or-create-scratch)) #+end_src - Font size #+begin_src elisp (defvar spook--font-size 11) (when (assoc 'small-screen spook--active-profiles) (setq spook--font-size 14)) (set-face-attribute 'default nil :height (* 10 spook--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) #+end_src #+begin_src elisp (use-package transient :ensure (transient :host github :repo "magit/transient" :branch "main")) (use-package magit :ensure (magit :host github :repo "magit/magit" :branch "main") :config (setq magit-display-buffer-function 'magit-display-buffer-fullframe-status-v1 magit-bury-buffer-function #'magit-restore-window-configuration)) #+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 (spook--defkeymap "spook-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 - 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 ** Org mode :PROPERTIES: :ID: 8b2528d8-3fd2-4076-8b1e-791df8ed9a67 :END: - Install latest org-mode. Elpaca will install the latest org-mode, instead of older version pre-packaged with emacs #+begin_src elisp (use-package org :config (eval-after-load 'org-mode (org-link-set-parameters "yt" :follow #'spook-org--follow-yt-link :export #'spook-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 t 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%U") ("t" "Todo" entry (file+headline org-inbox-file "Inbox") "* TODO %?\n%U\n[[%F]]")) 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(XP){+}" org-columns-default-format org-agenda-overriding-columns-format 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) ;; 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) (spook--defkeymap "spook-org" "C-c o" '("a" . org-agenda-list) '("A" . org-agenda) '("c" . org-capture) '("C" . org-clock-goto) '("o" . consult-org-agenda)) #+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 "Work" :tag "work" :order 1) (:name "In Progress" :todo "DOING" :order 1) (:name "Projects" :tag "project" :order 3) (:name "Home" :tag "home" :order 2) (:name "Study" :tag "study" :order 4) (:name "Inbox" :tag "inbox" :order 4) (:name "Habits" :tag "habit" :order 5)))) #+end_src - org-babel #+begin_src elisp (use-package ob-http) (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) (http . 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 spook--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 #'spook--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 spook-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 spook-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 ** 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)) (elpaca-wait) #+end_src - Normal mode-keybindings. Mostly mimicking vim #+begin_src elisp (meow-normal-define-key '("z" . spook-fold) '("/" . "C-s") '("?" . "C-r")) #+end_src - Leader keybindings #+begin_src elisp (meow-leader-define-key '("/" . consult-git-grep) '("p" . projectile-command-map) '("e" . flycheck-command-map) '("w" . ace-window) '("b" . spook-buffers) '("G" . spook-git) '("o" . spook-org) '("n" . spook-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 :ensure (:files (:defaults "extensions/*.el")) :init (vertico-mode +1) :config (define-key vertico-map (kbd "C-c ?") #'minibuffer-completion-help)) (use-package vertico-directory :after vertico :ensure nil ;; 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 :after vertico :ensure nil :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 :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 :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode +1)) #+end_src - Consult for enhanced commands #+begin_src elisp (use-package consult :init (setq consult-project-root-function #'projectile-project-root) :config (consult-customize consult-theme :preview-key '(:debounce 0.5 any)) (global-set-key (kbd "C-s") #'consult-line) (global-set-key (kbd "C-r") #'consult-line-multi) (global-set-key (kbd "C-x b") #'consult-buffer) (define-key spook-buffers-keymap (kbd "b") #'consult-buffer) (define-key spook-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 spook--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-dwim) ("C-h b" . embark-bindings) ("C-<" . spook--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 (use-package embrace :config (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 (use-package yasnippet :config (add-hook 'prog-mode-hook #'yas-minor-mode)) (use-package yasnippet-snippets :after yasnippet) #+end_src ** 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 (spook--defkeymap "spook-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 (use-package flycheck-eglot :ensure t :after (flycheck eglot) :config (global-flycheck-eglot-mode 1)) #+end_src - Projectile for managing projects. #+begin_src elisp (use-package projectile :init (projectile-mode +1) :bind (:map projectile-mode-map ("s-p" . projectile-command-map) ("C-c p" . projectile-command-map))) #+end_src - Company mode 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 (use-package company :init (global-company-mode +1)) #+end_src [[https://github.com/sebastiencs/company-box/][company-box-mode]] adds icons and colors to company options. #+begin_src elisp (use-package company-box :hook (company-mode . company-box-mode)) #+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) ;; 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 #+RESULTS: | garbage-collect | *** Treesitter #+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 When in NixOS, use system installed grammars. #+begin_src elisp (defvar nixos-p (s-contains-p "NixOS" (shell-command-to-string "uname -a"))) (when nixos-p (let ((nix-treesit-lib-path (expand-file-name "lib" (string-replace "\"" "" (string-trim (shell-command-to-string "nix eval nixpkgs#emacsPackages.treesit-grammars.with-all-grammars.outPath")))))) (setf treesit-extra-load-path (list nix-treesit-lib-path)))) #+end_src - Structured movement #+begin_src elisp (use-package combobulate :ensure (combobulate :host github :repo "mickeynp/combobulate") :preface ;; You can customize Combobulate's key prefix here. ;; Note that you may have to restart Emacs for this to take effect! (setq combobulate-key-prefix "C-c o") ;; 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)) :config (setf lispy-colon-p nil)) #+end_src Elsa provides very nice static-analysis and more for elisp programming. First time I am trying this, hopefully it does what it says on the box without much fuss. #+begin_src elisp (use-package flycheck-elsa :after elsa :hook (emacs-lisp-mode . flycheck-elsa-setup)) #+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 *** Nix :PROPERTIES: :ID: 54D0A9B3-1F7D-4E39-BBAD-E2266930C489 :END: #+begin_src elisp (use-package nix-mode :mode "\\.nix\\'") #+end_src *** Web dev :PROPERTIES: :ID: 62e08f15-d996-48fd-90c3-fd6d348555be :END: - Helper utilities - Are we using nvm? #+begin_src elisp (defun spook--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 spook--indent-width) (use-package js-ts :mode "\\.js'" :ensure nil :config (setq js-indent-level spook--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 spook--indent-width) (setq web-mode-code-indent-offset spook--indent-width) (setq web-mode-css-indent-offset spook--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) (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)))) (spook--defkeymap "spook-errors" "C-c e" '("n" . flycheck-next-error) '("p" . flycheck-previous-error) '("l" . flycheck-list-errors) '("e" . flycheck-explain-error-at-point)) (defun spook--setup-ts-js () "Setup Javascript and Typescript for current buffer." ;; Add node_modules/.bin of current project to exec-path. (if-let (nvm-bin (spook--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)))) ;; For 95% of cases this is what I want (prettier-format-on-save-mode +1) (eglot-ensure) (setf flymake-eslint-project-root (locate-dominating-file default-directory "package.json"))) (add-hook 'js-ts-mode-hook #'spook--setup-ts-js) (use-package typescript-ts-mode :mode "\\.ts\\'" :ensure nil :hook ((typescript-ts-mode . subword-mode)) :config (setq-default typescript-indent-level spook--indent-width) (add-hook 'typescript-mode-hook #'spook--setup-ts-js)) (use-package css-ts-mode :ensure nil :mode "\\.s?css\\'") #+end_src - JSON support #+begin_src elisp (use-package json-ts-mode :ensure nil :mode "\\.json\\'") #+end_src - Testing with jest #+begin_src elisp (use-package jest-test-mode :ensure t :commands jest-test-mode :hook (typescript-ts-mode) :config (setf jest-test-command-string "npm run test:only -- %s %s") (define-key typescript-ts-mode-map (kbd "C-c , t r") #'jest-test-run) (define-key typescript-ts-mode-map (kbd "C-c , t R") #'jest-test-rerun-test) (define-key typescript-ts-mode-map (kbd "C-c , t t") #'jest-test-run-at-point) (define-key typescript-ts-mode-map (kbd "C-c , t T") #'jest-test-debug-run-at-point) (define-key typescript-ts-mode-map (kbd "C-c , t d") #'jest-test-debug)) #+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-ts-mode :ensure nil :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 (spook--defkeymap "spook-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 :ensure t :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-projectile :after (treemacs projectile) :ensure t) (use-package treemacs-magit :after (treemacs magit) :ensure t) (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)) #+end_src Modeline #+begin_src elisp (use-package doom-modeline :init (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. - 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 :ensure nil :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 :ensure (denote :type git :host github :repo "protesilaos/denote" :branch "main") :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 spook--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. #+begin_src elisp (defvar spook-notes-mode-map (make-sparse-keymap)) (define-key spook-notes-mode-map (kbd "C-c i t") #'spook--insert-yt-ts-note) (define-minor-mode spook-notes-mode "Minor mode for taking spooky notes. It is used to set local keybindings depending on the kind of note being taken." :keymap spook-notes-mode-map) (defun spook--get-ff-yt-current-time () "Return current time of youtube video running in Firefox's active tab." (spookfox-eval-js-in-active-tab (concat "(function () {" "try {" "const player = document.querySelector('.video-stream');" "return { time: player.currentTime, url: `${window.location.href}&t=${Math.floor(player.currentTime)}` };" "} catch(e) { return 0; }" "})()") t)) (defun spook--insert-yt-ts-note (&optional url) "Insert note for current timestamp for URL in youtube. Inserted time is an org yt:// link to youtube video at that time." (interactive) (let* ((result (spook--get-ff-yt-current-time)) (time (plist-get result :time)) (url (string-replace "https" "yt" (plist-get result :url)))) (insert (concat "- At [[" url "][" (format-seconds "%m:%s" time) "]]\n")))) (defun spook--url-equal-p (url1 url2) "Return t if URL1 and URL2 have same host, query and path." (let ((url1 (url-generic-parse-url url1)) (url2 (url-generic-parse-url url2))) (and (equal (url-host url1) (url-host url2)) (equal (url-path-and-query url1) (url-path-and-query url2))))) (defun spook--find-denote-for-ff-tab (url &optional subdir) "Find existing denote entry for firefox tab for URL in denote SUBDIR. If previously a note for URL was being taken, return that file; nil otherwise." (let ((case-fold-search t) (subdir (expand-file-name subdir denote-directory)) (source-rx (rx "#+source: " (group (+ any) (not "#"))))) (seq-find (lambda (file) (with-temp-buffer (insert-file-contents file) (search-forward-regexp source-rx nil t) (spook--url-equal-p url (string-trim (or (match-string 1) ""))))) (mapcar (lambda (f) (expand-file-name f subdir)) (cl-remove-if-not (lambda (f) (string-match-p ".org$" f)) (directory-files subdir)))))) (defun spook--denote-ff-tab () "Create a new denote for current Firefox tab." (interactive) (let* ((tab (spookfox-request-active-tab)) (url (plist-get tab :url)) (yt-p (string-match-p "youtube.com" url)) (tags '("reading")) (existing-denote (spook--find-denote-for-ff-tab url "reading"))) (if existing-denote (find-file existing-denote) (when yt-p (push "video" tags)) (denote (denote-title-prompt (plist-get tab :title)) tags "org" (expand-file-name "reading" denote-directory)) (when yt-p (spook-notes-mode)) (delete-region (point) (line-beginning-position 0)) (insert (concat "#+source: " url "\n\n"))))) (defun spook--micro-post () "Quickly create a micro-post." (interactive) (let* ((body (read-from-minibuffer "Micro-Post body: ")) (title (denote-title-prompt (concat (string-trim (substring body 0 (min (length body) 40))) (when (> (length body) 40) "..."))))) (denote title '("micro" "blog-post")) (delete-region (point) (line-beginning-position 0)) (insert "#+published-on: ((mastodon . \"\"))\n\n") (insert body))) #+end_src - CRM #+begin_src elisp (defvar crm-directory (expand-file-name "crm" denote-directory)) (defun spook-crm--open-or-create () "Find or create CRM entry." (interactive) (let ((denote-directory crm-directory)) (call-interactively #'denote-open-or-create))) (defun spook-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 (spook--defkeymap "spook-notes" "C-c n" '("n" . denote-open-or-create) '("N" . denote-link-or-create) '("b" . denote-link-backlinks) '("d" . spook--diary-today) '("r" . spook--denote-ff-tab) '("p" . spook-crm--open-or-create) '("P" . spook-crm--link-or-create) '("m" . spook--micro-post)) #+end_src - Diary #+begin_src elisp (defun spook--find-habit (title) "Find the habit with TITLE in current buffer." (cl-block 'spook--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 'spook--find-habit el))))))) (defun spook--mark-habit-as-done (habit) "Mark HABIT as done." (with-current-buffer (find-file-noselect (expand-file-name "TODOs.org" org-directory)) (org-mode) (let ((el (cl-case habit (diary (spook--find-habit "write diary entry"))))) (goto-char (org-element-property :begin el)) (org-todo 'done)))) (defun spook--diary-today () "Go to today's diary entry." (interactive) (let ((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))) (spook--mark-habit-as-done 'diary) (denote title '("diary"))))) #+end_src - Work notes #+begin_src elisp (defun spook--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)))) (spook--defkeymap "workday" "C-c n w" '("w" . spook--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 :init (dirvish-override-dired-mode) :config (setq dirvish-attributes '(vc-state subtree-state all-the-icons collapse file-size)) :bind (("C-c f" . dirvish-fd) :map dirvish-mode-map ("/" . dirvish-narrow) ("a" . dirvish-quick-access) ("f" . dirvish-file-info-menu) ("y" . dirvish-yank-menu) ("N" . dirvish-narrow) ("^" . dirvish-history-last) ("h" . dirvish-history-jump) ; remapped `describe-mode' ("s" . dirvish-quicksort) ; remapped `dired-sort-toggle-or-edit' ("v" . dirvish-vc-menu) ; remapped `dired-view-file' ("TAB" . dirvish-subtree-toggle) ("M-f" . dirvish-history-go-forward) ("M-b" . dirvish-history-go-backward) ("M-l" . dirvish-ls-switches-menu) ("M-m" . dirvish-mark-menu) ("M-t" . dirvish-layout-toggle) ("M-s" . dirvish-setup-menu) ("M-e" . dirvish-emerge-menu) ("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 (when (file-exists-p "~/Documents/work/spookfox") (use-package spookfox :ensure (spookfox :type git :repo "~/Documents/work/spookfox" :files ("lisp/*.el" "lisp/apps/*.el")) :config (setq spookfox-enabled-apps (list spookfox-jscl spookfox-tabs spookfox-js-injection)) (setq spookfox-saved-tabs-target `(file+headline ,(expand-file-name "spookfox.org" org-directory) "Tabs")) (spookfox-init)) (defun spook--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 spook-buffers-keymap (kbd "t") #'spook--switch-tab-and-focus)) #+end_src - saunf Use the local repo; very risky, should change. #+begin_src elisp (when (file-exists-p (expand-file-name "~/Documents/work/saunf")) (use-package saunf :after sly :ensure (saunf :type git :repo "~/Documents/work/saunf" :files ("src/saunf.el")))) #+end_src - org-noter #+begin_src elisp (use-package nov :mode ("\\.epub\\'" . nov-mode)) ;; (use-package org-noter) #+end_src - Shelldon Let's try replacing alacritty with async-shell-command #+begin_src elisp (use-package shelldon :ensure (shelldon :type git :host github :repo "Overdr0ne/shelldon" :branch "master" :files ("shelldon.el")) :config (setq shell-command-switch "-ic") (add-hook 'shelldon-mode-hook 'ansi-color-for-comint-mode-on) (add-to-list 'comint-output-filter-functions 'ansi-color-process-output) (autoload 'ansi-color-for-comint-mode-on "ansi-color" nil t) (global-set-key (kbd "M-s") #'shelldon) (global-set-key (kbd "M-S") #'shelldon-loop)) #+end_src Shelldon recommends installing bash-complete. #+begin_src elisp (use-package bash-completion :config (autoload 'bash-completion-dynamic-complete "bash-completion" "BASH completion hook") (add-hook 'shell-dynamic-complete-functions 'bash-completion-dynamic-complete)) #+end_src - Enable listing shelldon buffers. Shelldon hides its buffers as soon as output window is hidden. That is fine for one-off commands, but I also run long-running commands like dev-servers etc, which need to be closed manually and also need to check the output for errors. #+begin_src elisp (defvar shell-output-history nil) (defun spook--switch-shell-output () "Select shelldon output buffers." (interactive) (consult-buffer (list `(:name "Shell Output" :narrow 98 :category buffer :face consult-buffer :history shell-output-history :state consult--buffer-state :default t :items (lambda () (consult--buffer-query :exclude nil :include "shelldon" :as #'buffer-name)))))) (define-key spook-buffers-keymap (kbd "o") #'spook--switch-shell-output) #+end_src - Quick helper to delete all shelldon buffers, because sometimes there are a lot of them. It is hard to delete them because they are hidden and don't show up in ibuffer #+begin_src elisp (defun spook--delete-all-shelldon-buffers () (interactive) (cl-dolist (buf (cl-remove-if-not (lambda (buf) (s-contains-p "*shelldon" (buffer-name buf))) (buffer-list))) (kill-buffer buf))) #+end_src - Irc Small utility to quickly connect to irc. #+begin_src elisp ;; (setq ;; erc-nick "bitspook" ;; erc-password (auth-source-pass-get 'secret "libera.chat/bitspook") ;; erc-autojoin-channels-alist '(("libera.chat" "#commonlisp" "#emacs" "#emacs-berlin" "#clschool" "#whereiseveryone" "#lispcafe"))) #+end_src - Terraform #+begin_src elisp (use-package terraform-mode) #+end_src - Eww #+begin_src elisp (add-hook 'eww-mode-hook (lambda () (display-line-numbers-mode -1))) #+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 ** Private work related config :PROPERTIES: :ID: 6CA859BF-001A-48A8-8FAF-A522EE7FC8B1 :END: #+begin_src elisp (elpaca-wait) (let ((private-config (expand-file-name "./private.el" user-emacs-directory))) (when (file-exists-p private-config) (load-file private-config))) #+end_src