- Clone this repo to ~/.emacs.d
- Open
readme.org
in emacs- Press
C-c C-v t
to tangle all the blocks- Restart Emacs. Elpaca will now clone all the packages, and then you’ll be ready for a spooky Emacs.
This setup make certain assumptions regarding its environment:
- Machine in use is Linux or MacOS
- Shell in use is
fish
- Bootstrap
;; -*- 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)
;; -*- 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))
- Install
use-package
;; 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)
- Ergonomically create keymaps
(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)))
- Utilities for helping with scratch buffer
This exactly function was introduced at or before Emacs version 29.1.
(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))))
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.
(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)))
- 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)
(defvar spook--active-profiles '() "Change things slightly based on different profiles.")
- On mac without external monitor
(when (eq system-type 'darwin) (push '(small-screen . t) spook--active-profiles))
- On mac without external monitor
- Start emacs as a server
(server-start)
- Unset annoying keybindings
(global-unset-key (kbd "C-x C-z")) (global-unset-key (kbd "C-z")) (global-unset-key (kbd "C-h h"))
- Set a custom-file so Emacs won’t put customized entries in my
init.el
which gets overwritten every time I tangle spookmax.d(setq custom-file (concat user-emacs-directory "custom.el"))
- Disable the ugly-ass toolbar, scroll-bars and menu-bar
(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)
- Make emacs a little transparent
(set-frame-parameter (selected-frame) 'alpha '(98 . 95)) (add-to-list 'default-frame-alist '(alpha . (98 . 95)))
- Disable native-comp warnings
(setq native-comp-async-report-warnings-errors 'silent)
- UI fixes copied from Doom
https://github.com/hlissner/doom-emacs/blob/develop/core/core-ui.el
- Scrolling
;;; 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)
- Cursors
;;; 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)
- Window/Frame
;; 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)
- Minibuffer
;; ;;; 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)
- Scrolling
- 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.
(delete-selection-mode +1)
- Indentation and whitespace
(setq spook--indent-width 2) (setq-default tab-width spook--indent-width) (setq-default indent-tabs-mode nil)
From: https://github.com/susam/emfy/blob/main/.emacs#L26
(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)
I got sick of manually calling whitespace cleanup all the trim. Cleanup whitespace.
(use-package whitespace-cleanup-mode :config (global-whitespace-cleanup-mode +1))
- Fill column for auto-formatting/filling paragraphs.
(setq-default fill-column 100)
- Introspection
Setup
which-key
for easy keys discovery(use-package which-key :config (which-key-mode t))
- Highlighting
(global-hl-line-mode +1) (use-package highlight-symbol :hook (prog-mode . highlight-symbol-mode) :config (setq highlight-symbol-idle-delay 0.3))
- Line numbers
(global-display-line-numbers-mode 1)
- Window management
- Custom window keybindings
(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))
- Install ace-window for some nice utilities.
(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)))
- Custom window keybindings
- 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
(use-package perspective :init (setq persp-mode-prefix-key (kbd "C-c C-w")) :config (persp-mode +1))
- Buffer management
(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))
- Font size
(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))
- [Ma]git
Magit uses
project-switch-commands
which are present only in more recent project.el project.(use-package project)
(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))
- git-link so I can copy link to lines in files because evidently, I am doing that a lot
(use-package git-link :config (setf git-link-use-commit t))
- Buncha nice keybindings.
(spook--defkeymap "spook-git" "C-c g" '("s" . magit-status) '("b" . magit-blame) '("g" . magit-dispatch))
- Magit Forge
;; (use-package forge ;; :after magit)
- git-link so I can copy link to lines in files because evidently, I am doing that a lot
- Keep backup/auto-save files out of my vc
(setq backup-dir "~/.emacs.d/bakups" backup-directory-alist `((".*" . ,backup-dir)) auto-save-file-name-transforms `((".*" ,backup-dir t)) create-lockfiles nil)
- Setup PATH from shell
(use-package exec-path-from-shell :config (exec-path-from-shell-initialize))
Libraries used elsewhere.
(use-package plz)
- Install latest org-mode. Elpaca will install the latest org-mode, instead
of older version pre-packaged with emacs
(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))))
- Other settings
(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(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 ;; let's try these out org-use-speed-commands t) ;; org-mode settings (with-eval-after-load 'org (org-indent-mode t) (require 'org-id))
- Keybindings
(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))
- org-super-agenda
More/better structure in agenda view.
(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 "Inbox" :tag "inbox" :order 4) (:name "Projects" :tag "project" :order 3) (:name "Home" :tag "home" :order 2) (:name "Study" :tag "study" :order 4) (:name "Health" :tag "health" :order 5) (:name "Habits" :tag "habit" :order 5))))
- org-babel
(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))))
- 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.
(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 "<div " (if class-tag (format "class=\"%s\" " class-tag)) (if id-tag (format "id=\"%s\" " id-tag)) (if data-attr (format "data-%s=\"%s\" " (car data-attr) (cadr data-attr))) ">" old-ret "</div>") old-ret))) (advice-add 'org-html-src-block :around #'spook--org-src-block-html-attrs-advice)
- Support exporting code blocks with syntax-highlighting
(use-package htmlize)
- Custom links
yt://
links- Open
yt://
links inmpv
if mpv is present - Open
yt://
links in browser if mpv isn’t installed or prefix-argument is provided withorg-open-at-point
(i.eC-c C-o
)
(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 "<iframe width=\"560\" height=\"315\" src=\"%s\" title=\"%s\" frameborder=\"0\" allowfullscreen></iframe>" url desc))))
- Open
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
(defun meow-setup () (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty) (meow-motion-overwrite-define-key '("j" . meow-next) '("k" . meow-prev) '("<escape>" . 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) '("<escape>" . ignore)))
(use-package meow
:config
(setf meow-use-clipboard t)
(meow-global-mode)
(meow-setup))
(elpaca-wait)
- Normal mode-keybindings. Mostly mimicking vim
(meow-normal-define-key '("z" . spook-fold) '("/" . "C-s") '("?" . "C-r"))
- Leader keybindings
(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))
- Keychords
(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))
- Orderlies adds matches completion candidates by space-separated patterns in
any order
(use-package orderless :config (setq completion-styles '(orderless)))
- Vertico for completion UI
(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)
- Marginalia adds pretty information to completions. It’s pretty, useful, and
recommended by
embark
(it provides extra information toembark
);; Enable richer annotations using the Marginalia package (use-package marginalia :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)) :init (marginalia-mode +1))
- Consult for enhanced commands
(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))
- embark allow contextual actions, like opening buffers in other window from
minibuffer and a lot more
(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-act-all) ("C-." . embark-dwim) ("C-h b" . embark-bindings) ("C-<" . spook--embark-act-no-quit)))
wgrep
for editing grep buffers(use-package wgrep)
undo-tree-mode
for more powerful undo(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)))))
embrace
for wrapping pair manipulation(use-package embrace :config (add-hook 'org-mode-hook 'embrace-org-mode-hook) (meow-normal-define-key '("S" . embrace-commander)))
- yasnippet for templates
(use-package yasnippet :config (add-hook 'prog-mode-hook #'yas-minor-mode)) (use-package yasnippet-snippets :after yasnippet)
- Show trailing whitespace in programming files
(add-hook 'prog-mode-hook #'(lambda () (setq-local show-trailing-whitespace t)))
- Wrapping text in parens, quotes etc
(show-paren-mode 1) (electric-pair-mode 1)
- Code folding
(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)
- Flycheck for getting those in-buffer warnings errors.
(use-package flycheck :init (global-flycheck-mode t) ;; alias is needed for using the keymap in meow (defalias 'flycheck-command-map flycheck-command-map))
Flycheck eglot integration
(use-package flycheck-eglot :ensure t :after (flycheck eglot) :config (global-flycheck-eglot-mode 1))
- Projectile for managing projects.
(use-package projectile :init (projectile-mode +1) :bind (:map projectile-mode-map ("s-p" . projectile-command-map) ("C-c p" . projectile-command-map)))
- 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.
(use-package company :init (global-company-mode +1))
company-box-mode adds icons and colors to company options.
(use-package company-box :hook (company-mode . company-box-mode))
- Reformatter allow creating buffer/region formatters from any command.
(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)))
- Direnv is pretty essential for my dev workflow.
(use-package direnv :config (direnv-mode) (when (not (boundp 'warning-suppress-types)) (setq warning-suppress-types nil)) (add-to-list 'warning-suppress-types '(direnv)))
- Eglot to provide LSP support.
;; 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)
(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")))
When in NixOS, use system installed grammars.
(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))))
- Structured movement
(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)))
Lispy for some nasty lisp structural editing.
(use-package lispy
:hook ((emacs-lisp-mode . lispy-mode)
(lisp-mode . lispy-mode))
:config
(setf lispy-colon-p nil))
Pretty cool auto-indentation.
(use-package aggressive-indent
:hook ((emacs-lisp-mode . aggressive-indent-mode)
(lisp-mode . aggressive-indent-mode)))
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.
(use-package flycheck-elsa
:after elsa
:hook (emacs-lisp-mode . flycheck-elsa-setup))
- Common Lisp
Sly for interactive development.
(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"))))
sly-asdf add asdf integration to sly.
(use-package sly-asdf :config (add-to-list 'sly-contribs 'sly-asdf 'append))
(use-package nix-mode
:mode "\\.nix\\'")
- Helper utilities
- Are we using nvm?
(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))
- Are we using nvm?
(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)
(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))))
(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 tsx-ts-mode
:mode "\\.tsx\\'"
:ensure nil
:hook ((tsx-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\\'")
- JSON support
(use-package json-ts-mode :ensure nil :mode "\\.json\\'")
- Testing with jest
(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))
(use-package rustic
:init
(setq rustic-cargo-bin "cargo")
(push 'rustic-clippy flycheck-checkers))
(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))
(use-package yaml-ts-mode
:ensure nil
:mode "\\.ya?ml\\'")
(use-package graphql-mode
:mode "\\.graphql\\'")
(when (file-exists-p "/gnu/store/")
(use-package guix)
(use-package geiser)
(use-package geiser-guile
:after (yasnippet guix)
:config
(when (file-exists-p "~/code/guix")
(add-to-list 'geiser-guile-load-path "~/code/guix")
(add-to-list 'yas-snippet-dirs "~/code/guix/etc/snippets/yas"))
(add-hook 'scheme-mode-hook #'guix-devel-mode)
(add-hook 'scheme-mode-hook #'lispy-mode)))
Nice to have features but not necessary.
- Ace Jump for quickly jumping around in a buffer
(spook--defkeymap "spook-jump" "C-c q" '("q" . ace-jump-mode) '("w" . ace-jump-word-mode)) (use-package ace-jump-mode)
- Treemacs for easy code exploration
(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"))
- Highlight indentation
(use-package highlight-indent-guides :config (setf highlight-indent-guides-method 'bitmap) (add-hook 'prog-mode-hook 'highlight-indent-guides-mode))
- Move text around
(use-package move-text :config (move-text-default-bindings))
;; (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))
Modeline
(use-package doom-modeline
:init
(setq doom-modeline-height 24)
(doom-modeline-mode 1))
Let’s also try smooth-scrolling.
(pixel-scroll-precision-mode t)
Non crucial things which should be loaded last. If they fail, nothing crucial is blocked.
- Spell checking
(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"))
(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))
- Notes using denotes
(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))
- Enhance denote a bit, don’t know why these aren’t a part of denote itself.
(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)))
- Setup for taking notes for reading/video-watching I do in Firefox.
(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-key spook-notes-mode-map (kbd "C-c i u") #'spook--insert-current-url) (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--insert-current-url () "Insert current tab's URL." (interactive) (insert (spookfox-js-injection-eval-in-active-tab "window.location.href" t))) (defun spook--get-ff-yt-current-time () "Return current time of youtube video running in Firefox's active tab." (interactive) (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) "]]")))) (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)) (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)))
- CRM
(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)))
- Keyboard shortcuts for fluent note-taking/reading
(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))
- Diary
(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")))))
- Work notes
(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))
- Enhance denote a bit, don’t know why these aren’t a part of denote itself.
- dirvish for more powerful dired
(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)))
- Ledger
(use-package ledger-mode :mode "\\.ledger\\'" :config (setq ledger-default-date-format ledger-iso-date-format))
- spookfox
(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))
- saunf
Use the local repo; very risky, should change.
(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"))))
- org-noter
(use-package nov :mode ("\\.epub\\'" . nov-mode) :init (add-hook 'nov-mode-hook #'shrface-mode) :config (require 'shrface) (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))) ;; (use-package org-noter)
- Shelldon
Let’s try replacing alacritty with async-shell-command
(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))
Shelldon recommends installing bash-complete.
(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))
- 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.
(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)
- 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
(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)))
- Enable listing shelldon buffers.
- Irc
Small utility to quickly connect to irc.
(use-package erc :ensure nil :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")))) (use-package erc-track :ensure 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))
- Terraform
(use-package terraform-mode)
- Eww
(setf eww-readable-urls '(".*")) (add-hook 'eww-mode-hook (lambda () (display-line-numbers-mode -1)))
Syntax-highlighting for code blocks in HTML.
(use-package shr-tag-pre-highlight :after shr :config (add-to-list 'shr-external-rendering-functions '(pre . shr-tag-pre-highlight)))
Make EWW buffer niceer with org-outline like features.
(use-package shrface :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)) ;; I don't use Anki.el, but I am likely to in near future. Leaving this here as reminder. ;; (use-package anki ;; :defer t ;; :load-path "~/.emacs.d/lisp/anki/" ;; :init ;; (add-hook 'anki-mode-hook #'shrface-mode) ;; (autoload 'anki "anki") ;; (autoload 'anki-browser "anki") ;; (autoload 'anki-list-decks "anki") ;; :config ;; (require 'shrface) ;; (setq anki-shr-rendering-functions (append anki-shr-rendering-functions shr-external-rendering-functions)) ;; (setq sql-sqlite-program "/usr/bin/sqlite3") ;; (setq anki-collection-dir "/Users/chandamon/Library/Application Support/Anki2/User 1"))
- org-download to easily attach images in my notes
(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))
- Eat
More terminal-like experience for some CLI/TUI tools that just need it.
(use-package eat :ensure (eat :type git :repo "https://codeberg.org/akib/emacs-eat" :files ("*.el" ("term" "term/*.el") "*.texi" "*.ti" ("terminfo/e" "terminfo/e/*") ("terminfo/65" "terminfo/65/*") ("integration" "integration/*") (:exclude ".dir-locals.el" "*-tests.el"))))
- My workflows
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.(dolist (file (directory-files user-emacs-directory t "workflow-.*\\.el")) (load-file file))
(elpaca-wait)
(let ((private-config (expand-file-name "./private.el" user-emacs-directory)))
(when (file-exists-p private-config)
(load-file private-config)))