#+TITLE: Emacs #+STARTUP: content * Configuration :emacs: ** About my Emacs This is my detailed Emacs configuration. It's an ~org~ file that is transpiled to ~emacs-lisp~ as part of the Nix build process. Why an org file? My Emacs config is large, and this enables me to greatly improve readability of its documentation. I edit this file the same way you're reading it (nicely formatted) as I use Emacs and Emacs speaks org. Sounds complicated, but it's really not; [[https://github.com/dustinlyons/nixos-config/blob/main/nixos/default.nix#L215][just a few lines]] in my Nix config. [[https://github.com/dustinlyons/nixos-config/blob/main/darwin/default.nix#L28][MacOS too.]] This is the main configuration, but there also exists one more init file, ~init.el~ , that bootstraps ~org-mode~ before this file is interpreted. That's defined [[https://github.com/dustinlyons/nixos-config/blob/main/shared/files.nix#L5][here]]. /Each block of code below is the actual Emacs configuration./ Formally, this style of configuration is named [[https://en.wikipedia.org/wiki/Literate_programming]["literate programming"]]. ** Personal Information Just me! #+NAME: personal-info #+BEGIN_SRC emacs-lisp (setq user-full-name "Jip J. Dekker" user-mail-address "jip@dekker.one") #+END_SRC ** Initialization *** Booting up Mainly splash screen settings. In the future we may look to optimize performance here. #+NAME: startup #+BEGIN_SRC emacs-lisp ;; Turn off the splash screen (setq inhibit-startup-screen t) ;; Turn off the splash screen (setq initial-scratch-message nil) ;; Confirm before exiting Emacs (setq confirm-kill-emacs #'yes-or-no-p) ;; Set default frame size and position (defun adjust-frame-size-and-position (&optional frame) "Adjust size and position of FRAME based on its type." (if (display-graphic-p frame) (let* ((w 150) ; Set to desired width in characters (h 50) ; Set to desired height in lines (width (* w (frame-char-width frame))) (height (* h (frame-char-height frame))) (left (max 0 (floor (/ (- (x-display-pixel-width) width) 2)))) (top (max 0 (floor (/ (- (x-display-pixel-height) height) 2))))) (set-frame-size frame w h) (set-frame-position frame left top)) ;; Ensure the menu bar is off in terminal mode (when (and (not (display-graphic-p frame)) (menu-bar-mode 1)) (menu-bar-mode -1)))) (if (daemonp) (add-hook 'after-make-frame-functions (lambda (frame) (select-frame frame) (when (system-is-mac) (adjust-frame-size-and-position frame))) (adjust-frame-size-and-position))) #+END_SRC *** Add package sources This associates our package manager with the right source (MELPA). #+NAME: package-sources #+BEGIN_SRC emacs-lisp (unless (assoc-default "melpa" package-archives) (add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/") t)) (unless (assoc-default "nongnu" package-archives) (add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/") t)) #+END_SRC *** System Definitions **** Conditionals #+BEGIN_SRC emacs-lisp (defun system-is-mac () "Return true if system is darwin-based (Mac OS X)" (string-equal system-type "darwin")) (defun system-is-linux () "Return true if system is GNU/Linux-based" (string-equal system-type "gnu/linux")) ;; Set path for darwin (when (system-is-mac) (setenv "PATH" (concat (getenv "PATH") ":/Users/dekker1/.nix-profile/bin:/usr/bin")) (setq exec-path (append '("/Users/dekker1/bin" "/profile/bin" "/Users/dekker1/.npm-packages/bin" "/Users/dekker1/.nix-profile/bin" "/nix/var/nix/profiles/default/bin" "/usr/local/bin" "/usr/bin") exec-path))) #+END_SRC *** Counsel/Ivy framework Ivy and associated helpers that uses the minibuffer. Ivy describes itself as "a generic completion mechanism for Emacs." Basically, it's a prettier popup window to input Emacs commands. I've defined this to show at the bottom of the screen, which is conveniently also the default. #+NAME: ivy-framework #+BEGIN_SRC emacs-lisp (use-package counsel :demand t :bind (("M-x" . counsel-M-x) ("C-x b" . counsel-ibuffer) ("C-x C-f" . counsel-find-file) ("C-M-j" . counsel-switch-buffer) :map minibuffer-local-map ("C-r" . 'counsel-minibuffer-history)) :custom (counsel-linux-app-format-function #'counsel-linux-app-format-function-name-only) :config (setq ivy-initial-inputs-alist nil)) ;; Don't start searches with ^ (use-package prescient :config (prescient-persist-mode 1)) (use-package ivy :bind (("C-s" . swiper-all) :map ivy-minibuffer-map ("TAB" . ivy-partial-or-done) ("C-f" . ivy-alt-done) ("C-l" . ivy-alt-done) ("C-j" . ivy-next-line) ("C-k" . ivy-previous-line) :map ivy-switch-buffer-map ("C-k" . ivy-previous-line) ("C-l" . ivy-done) ("C-d" . ivy-switch-buffer-kill) :map ivy-reverse-i-search-map ("C-k" . ivy-previous-line) ("C-d" . ivy-reverse-i-search-kill)) :init (ivy-mode 1) :config (setq ivy-use-virtual-buffers t) (setq ivy-wrap t) (setq ivy-count-format "(%d/%d) ") (setq enable-recursive-minibuffers t)) (use-package ivy-rich :init (ivy-rich-mode 1)) (use-package ivy-prescient :after ivy :custom (prescient-save-file "~/.emacs.d/prescient-data") (prescient-filter-method 'fuzzy) :config (ivy-prescient-mode t)) (use-package all-the-icons-ivy :init (add-hook 'after-init-hook 'all-the-icons-ivy-setup)) #+END_SRC *** Leader keys I use ~general.el~ to define groups of keybindings under my 'leader' definition. You will see these definitions sprinkled throughout this file; they are just quick shortcuts. For more info, [[https://medium.com/usevim/vim-101-what-is-the-leader-key-f2f5c1fa610f][here]] is a good explanation on leader keys. #+NAME: keybindings #+BEGIN_SRC emacs-lisp ;; ESC will also cancel/quit/etc. (global-set-key (kbd "") 'keyboard-escape-quit) (use-package general :init (setq evil-want-keybinding nil) :config (general-evil-setup t) (general-create-definer dl/leader-keys :keymaps '(normal visual emacs) :prefix ",")) #+END_SRC **** Emacs cleanup Helpful keybindings to help keep Emacs sane. #+NAME: emacs-cleanup #+BEGIN_SRC emacs-lisp (dl/leader-keys "k" '(:ignore k :which-key "cleanup") "ko" '(kill-buffer-and-window :which-key "kill buffer and window") "kk" '(kill-some-buffers :which-key "cleanup buffers")) (global-set-key (kbd "C-x -") 'kill-buffer-and-window) #+END_SRC **** Treemacs #+NAME: treemacs #+BEGIN_SRC emacs-lisp (dl/leader-keys "t" '(:ignore t :which-key "treemacs") "tt" '(treemacs :which-key "toggle treemacs") "tx" '(treemacs-collapse-all-projects :which-key "collapse projects") "to" '(treemacs-select-window :which-key "select treemacs") "tw" '(treemacs-toggle-fixed-width :which-key "size treemacs")) #+END_SRC **** Toggles Various UI related toggles. #+NAME: toggles-ui #+BEGIN_SRC emacs-lisp (dl/leader-keys "h" '(counsel-load-theme :which-key "choose theme")) #+END_SRC ***** Rotate windows Various helpers and packages I find useful for window management. #+BEGIN_SRC emacs-lisp ;; Rotates windows and layouts (use-package rotate :config) (dl/leader-keys "r" '(:ignore t :which-key "rotate") "rw" '(rotate-window :which-key "rotate window") "rl" '(rotate-layout :which-key "rotate layout")) #+END_SRC *** Gutter **** Line numbers These functions define vim-style relative line numbers. This means my line numbers look like -1, -2, 0, 1, 2... *** Modes **** Window minor modes I like these window related minor modes. #+NAME: windows-ui-settings #+BEGIN_SRC emacs-lisp ;; Turn off UI junk ;; Note to future self: If you have problems with these later, ;; move these into custom file and set variable custom-file (column-number-mode) (scroll-bar-mode 0) (menu-bar-mode -1) (tool-bar-mode 0) (winner-mode 1) ;; ctrl-c left, ctrl-c right for window undo/redo #+END_SRC **** Set mode margins This is used primarily to center org mode text. #+NAME: mode-margins #+BEGIN_SRC emacs-lisp (defun dl/org-mode-visual-fill () (setq visual-fill-column-width 110 visual-fill-column-center-text t)) (use-package visual-fill-column :defer t :hook (org-mode . dl/org-mode-visual-fill)) #+END_SRC **** Don't blink the cursor #+NAME: cursor-mode #+BEGIN_SRC emacs-lisp (blink-cursor-mode -1) #+END_SRC **** Colors ***** Rainbow delimiters Makes my lisp parens pretty, and easy to spot. #+NAME: rainbow-delmiters #+BEGIN_SRC emacs-lisp (use-package rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) #+END_SRC ***** Color definitions Define a global set of colors to be used everywhere in Emacs. #+NAME: color-definitions #+BEGIN_SRC emacs-lisp (defvar dl/black-color "#1F2528") (defvar dl/red-color "#EC5F67") (defvar dl/yellow-color "#FAC863") (defvar dl/blue-color "#6699CC") (defvar dl/green-color "#99C794") (defvar dl/purple-color "#C594C5") (defvar dl/teal-color "#5FB3B3") (defvar dl/light-grey-color "#C0C5CE") (defvar dl/dark-grey-color "#65737E") #+END_SRC **** Addons ***** "Powerline" Keeps info at my fingertips. Modeline is much better than Vim's Powerline (sorry Vim). #+NAME: modeline #+BEGIN_SRC emacs-lisp ;; Run M-x all-the-icons-install-fonts to install (use-package all-the-icons) (use-package doom-modeline :ensure t :init (doom-modeline-mode 1)) #+END_SRC ***** Treemacs Although I'm primarily a keyboard user and use Projectile for quickly finding files, I still find the need to browse through files in a more visual way. Treemacs does the job, and beautifully might I add. #+NAME: treemacs #+BEGIN_SRC emacs-lisp (use-package treemacs :config (setq treemacs-is-never-other-window 1) :bind ("C-c t" . treemacs-find-file) ("C-c b" . treemacs-bookmark)) (use-package treemacs-icons-dired) (use-package treemacs-all-the-icons) (use-package treemacs-projectile) (use-package treemacs-magit) (use-package treemacs-evil) #+END_SRC **** Easy window motions with ace-window Predefine windows with hotkeys and jump to them. #+NAME: easy-window-motions #+BEGIN_SRC emacs-lisp ;; Remove binding for facemap-menu, use for ace-window instead (global-unset-key (kbd "M-o")) (use-package ace-window :bind (("M-o" . ace-window)) :custom (aw-scope 'frame) (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)) (aw-minibuffer-flag t) :config (ace-window-display-mode 1)) #+END_SRC *** Package managers Using ~straight.el~ under the hood of ~use-package~ enables us to download packages using ~git~. This is preferred for easier hacking; I maintain my own ~org-roam~ fork, for example, and it's just another directory where I organize code. I configure ~straight.el~ with one line to use it. *** Windows **** Fonts #+NAME: fonts #+BEGIN_SRC emacs-lisp ;; Set the default pitch face (when (system-is-linux) (set-face-attribute 'default nil :font "JetBrainsMono" :height 100)) (when (system-is-mac) (set-face-attribute 'default nil :font "JetBrains Mono" :height 140)) ;; Set the fixed pitch face (when (system-is-linux) (set-face-attribute 'fixed-pitch nil :font "JetBrainsMono" :weight 'normal :height 100)) (when (system-is-mac) (set-face-attribute 'fixed-pitch nil :font "JetBrains Mono" :weight 'normal :height 150)) ;; Set the variable pitch face (when (system-is-linux) (set-face-attribute 'variable-pitch nil :font "Helvetica LT Std Condensed" :weight 'normal :height 140)) (when (system-is-mac) (set-face-attribute 'variable-pitch nil :font "Helvetica" :weight 'normal :height 170)) #+END_SRC *** Dashboard #+NAME: dashboard-settings #+BEGIN_SRC emacs-lisp (use-package dashboard :ensure t :config (dashboard-setup-startup-hook) (setq dashboard-startup-banner 'ascii dashboard-center-content t dashboard-items '((projects . 5) (recents . 5))) (setq dashboard-set-footer nil)) (setq dashboard-banner-logo-title "Welcome to your life") (setq dashboard-set-file-icons t) (setq dashboard-projects-backend 'projectile) (setq initial-buffer-choice (lambda () (get-buffer-create "*dashboard*") (dashboard-refresh-buffer))) (setq dashboard-projects-switch-function 'counsel-projectile-switch-project-by-name) #+END_SRC ** Keybindings *** Spaces over tabs We use two spaces in place of tabs. I don't even want to hear it. #+NAME: next-buffer #+BEGIN_SRC emacs-lisp (setq-default indent-tabs-mode nil js-indent-level 2 tab-width 2) (setq-default evil-shift-width 2) #+END_SRC *** Buffers #+NAME: next-buffer #+BEGIN_SRC emacs-lisp (global-set-key (kbd "") 'next-buffer) #+END_SRC ** Display options *** Themes **** Doom Emacs #+NAME: themes-autothemer #+BEGIN_SRC emacs-lisp (use-package doom-themes :ensure t :config (setq doom-themes-enable-bold t doom-themes-enable-italic t) (load-theme 'doom-one t) (doom-themes-visual-bell-config) (doom-themes-org-config)) #+END_SRC ** Global Settings *** Global Modes I like these modes, what can I say. They're good to me. #+NAME: global-modes #+BEGIN_SRC emacs-lisp (defalias 'yes-or-no-p 'y-or-n-p) ;; Use Y or N in prompts, instead of full Yes or No (global-visual-line-mode t) ;; Wraps lines everywhere (global-auto-revert-mode t) ;; Auto refresh buffers from disk (line-number-mode t) ;; Line numbers in the gutter (show-paren-mode t) ;; Highlights parans for me (setq warning-minimum-level :error) #+END_SRC ** Org mode *** Agenda Initialize org-agenda file and set some key bindings to create tasks. #+NAME::org-mode-agenda #+BEGIN_SRC emacs-lisp (setq org-agenda-files "~/.emacs.d/agenda.txt" ) (defun my-org-insert-subheading (heading-type) "Inserts a new org heading with unique ID and creation date. The type of heading (TODO, PROJECT, etc.) is specified by HEADING-TYPE." (let ((uuid (org-id-uuid)) (date (format-time-string "[%Y-%m-%d %a %H:%M]"))) (org-end-of-line) ;; Make sure we are at the end of the line (unless (looking-at-p "\n") (insert "\n")) ;; Insert newline if next character is not a newline (org-insert-subheading t) ;; Insert a subheading instead of a heading (insert (concat heading-type " ")) (save-excursion (org-set-property "ID" uuid) (org-set-property "CREATED" date)))) (defun my-org-insert-todo () "Inserts a new TODO heading with unique ID and creation date." (interactive) (my-org-insert-subheading "TODO")) (defun my-org-insert-project () "Inserts a new PROJECT heading with unique ID and creation date." (interactive) (my-org-insert-subheading "PROJECT")) (defun my-org-copy-link-from-id () "Copies a link to the current Org mode item by its ID to clipboard" (interactive) (when (org-at-heading-p) (let* ((element (org-element-at-point)) (title (org-element-property :title element)) (id (org-entry-get nil "ID")) (link (format "[[id:%s][%s]]" id title))) (when id (kill-new link) (message "Link saved to clipboard"))))) (define-prefix-command 'my-org-todo-prefix) (global-set-key (kbd "C-c c") 'org-capture) (global-set-key (kbd "C-c t") 'my-org-todo-prefix) (define-key 'my-org-todo-prefix (kbd "t") 'my-org-insert-todo) (define-key 'my-org-todo-prefix (kbd "p") 'my-org-insert-project) (define-key org-mode-map (kbd "C-c l") 'my-org-copy-link-from-id) #+END_SRC **** Set org faces Set various types and colors for ~org-mode~. #+NAME::org-mode-faces #+BEGIN_SRC emacs-lisp ;; Fast access to tag common contexts I use (setq org-todo-keywords '((sequence "TODO(t)" "STARTED(s)" "WAITING(w@/!)" "DELEGATED(g@/!)" "DEFERRED(r)" "SOMEDAY(y)" "|" "DONE(d@)" "CANCELED(x@)") (sequence "PROJECT(p)" "|" "DONE(d@)" "CANCELED(x@)") (sequence "APPT(a)" "|" "DONE(d@)" "CANCELED(x@)"))) (setq org-todo-keyword-faces `(("TODO" . ,dl/green-color) ("STARTED" . ,dl/yellow-color) ("WAITING" . ,dl/light-grey-color) ("DELEGATED" . ,dl/teal-color) ("DEFERRED" . ,dl/dark-grey-color) ("SOMEDAY" . ,dl/purple-color) ("DONE" . ,dl/dark-grey-color) ("CANCELED" . ,dl/dark-grey-color) ("PROJECT" . ,dl/blue-color) ("APPT" . ,dl/green-color))) (defface my-org-agenda-face-1-2 '((t (:inherit default :height 1.2))) "Face for org-agenda mode.") (defun my-set-org-agenda-font () "Set the font for `org-agenda-mode'." (buffer-face-set 'my-org-agenda-face-1-2)) (add-hook 'org-agenda-mode-hook 'my-set-org-agenda-font) (setq display-buffer-alist `((".*Org Agenda.*" (display-buffer-below-selected) (inhibit-same-window . t) (window-height . 0.5)))) #+END_SRC **** Format org-agenda views This block sets the ~org-agenda-prefix-format~ in an friendly way for ~org-roam~ (credit to [[https://d12frosted.io/posts/2020-06-24-task-management-with-roam-vol2.html][this post)]]. It truncates long filenames and removes the ~org-roam~ timestamp slug. #+NAME::org-agenda-prefixes #+BEGIN_SRC emacs-lisp (defun dl/buffer-prop-get (name) "Get a buffer property called NAME as a string." (org-with-point-at 1 (when (re-search-forward (concat "^#\\+" name ": \\(.*\\)") (point-max) t) (buffer-substring-no-properties (match-beginning 1) (match-end 1))))) (defun dl/agenda-category (&optional len) "Get category of item at point for agenda." (let* ((file-name (when buffer-file-name (file-name-sans-extension (file-name-nondirectory buffer-file-name)))) (title (dl/buffer-prop-get "title")) (category (org-get-category)) (result (or (if (and title (string-equal category file-name)) title category)))) (if (numberp len) (s-truncate len (s-pad-right len " " result)) result))) (evil-set-initial-state 'org-agenda-mode 'normal) (with-eval-after-load 'org-agenda (define-key org-agenda-mode-map (kbd "j") 'org-agenda-next-line) (define-key org-agenda-mode-map (kbd "k") 'org-agenda-previous-line)) (setq org-agenda-todo-ignore-keywords '("PROJECT")) #+END_SRC **** org-super-agenda views Setup for ~org-super-agenda~ and ~org-ql~. #+NAME::org-super-agenda #+BEGIN_SRC emacs-lisp (use-package org-super-agenda :after org-agenda :init (setq org-agenda-dim-blocked-tasks nil)) ;; Define custom faces for group highlighting (defface org-super-agenda-header '((t (:inherit org-agenda-structure :height 1.1 :foreground "#7cc3f3" :background "#282c34"))) "Face for highlighting org-super-agenda groups.") (defface org-super-agenda-subheader '((t (:inherit org-agenda-structure :height 1.0 :foreground "light slate gray" :background "black"))) "Face for highlighting org-super-agenda subgroups.") ;; Apply the custom faces to org-super-agenda (custom-set-faces '(org-super-agenda-header ((t (:inherit org-agenda-structure :height 1.1 :foreground "#7cc3f3" :background "#282c34")))) '(org-super-agenda-subheader ((t (:inherit org-agenda-structure :height 1.0 :foreground "light slate gray" :background "black"))))) (setq org-super-agenda-groups '((:name "Priority A" :priority "A") (:name "Priority B" :priority "B") (:name "Priority C" :priority "C") (:name "Started" :todo "STARTED") (:name "Waiting" :todo "WAITING") (:name "Tasks" :todo "TODO") (:name "Someday" :todo "SOMEDAY") (:name "Projects" :tag "PROJECT"))) (org-super-agenda-mode) #+END_SRC **** org-transclusion Let's us move text but still see it in another file. I primarily use this to move text around in my journal. #+NAME::org-transclusion #+BEGIN_SRC emacs-lisp (use-package org-transclusion :after org :hook (org-mode . org-transclusion-mode)) (defun org-global-props (&optional property buffer) "Helper function to grab org properties" (unless property (setq property "PROPERTY")) (with-current-buffer (or buffer (current-buffer)) (org-element-map (org-element-parse-buffer) 'keyword (lambda (el) (when (string-match property (org-element-property :key el)) el))))) #+END_SRC *** Install package If you haven't heard of org mode, go watch [[https://www.youtube.com/watch?v=SzA2YODtgK4][this]] talk and come back when you are finished. **** Leader key shortcuts #+NAME::org-mode-quick-entry #+BEGIN_SRC emacs-lisp (defvar current-time-format "%H:%M:%S" "Format of date to insert with `insert-current-time' func. Note the weekly scope of the command's precision.") (defun dl/find-file (path) "Helper function to open a file in a buffer" (interactive) (find-file path)) (defun dl/reload-emacs () "Reload the emacs configuration" (interactive) (load "~/.emacs.d/init.el")) (defun dl/insert-header () "Insert a header indented one level from the current header, unless the current header is a timestamp." (interactive) (let* ((level (org-current-level)) (headline (org-get-heading t t t t)) (next-level (if (string-match "^\\([0-9]\\{2\\}:[0-9]\\{2\\}:[0-9]\\{2\\}\\)" headline) (1+ level) level))) (end-of-line) (newline) (insert (make-string next-level ?*)) (insert " "))) (defun dl/insert-current-time () "Insert the current time into the current buffer, at a level one deeper than the current heading." (interactive) (let* ((level (org-current-level)) (next-level (1+ level))) (end-of-line) (newline) (insert (make-string next-level ?*)) (insert " " (format-time-string "%H:%M:%S" (current-time)) "\n"))) "Emacs relates shortcuts" (dl/leader-keys "e" '(:ignore t :which-key "emacs") "ee" '(dl/load-buffer-with-emacs-config :which-key "open emacs config") "er" '(dl/reload-emacs :which-key "reload emacs")) "A few of my own personal shortcuts" (dl/leader-keys "," '(dl/insert-header :which-key "insert header") "<" '(dl/insert-current-time :which-key "insert header with current time")) #+END_SRC ***** Roam capture templates These are templates used to create new notes. #+NAME::roam-templates #+BEGIN_SRC emacs-lisp (setq org-roam-capture-templates '(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n\n") :unnarrowed t))) #+END_SRC **** Org Roam ***** Install package #+NAME::org-roam-package #+BEGIN_SRC emacs-lisp (require 'ucs-normalize) (use-package org-roam :straight (:host github :repo "org-roam/org-roam" :branch "master" :files (:defaults "extensions/*") :build (:not compile)) :init (setq org-roam-v2-ack t) ;; Turn off v2 warning (setq org-roam-mode-section-functions (list #'org-roam-backlinks-section #'org-roam-reflinks-section #'org-roam-unlinked-references-section)) (add-to-list 'display-buffer-alist '("\\*org-roam\\*" (display-buffer-in-direction) (direction . right) (window-width . 0.33) (window-height . fit-window-to-buffer))) :custom (org-roam-directory (file-truename "~/.local/share/org-roam")) (org-roam-dailies-directory "daily/") (org-roam-completion-everywhere t) :bind (("C-c r b" . org-roam-buffer-toggle) ("C-c r t" . org-roam-dailies-goto-today) ("C-c r y" . org-roam-dailies-goto-yesterday) ("C-M-n" . org-roam-node-insert) :map org-mode-map ("C-M-i" . completion-at-point) ("C-M-f" . org-roam-node-find) ("C-M-c" . dl/org-roam-create-id) ("C-" . org-roam-dailies-goto-previous-note) ("C-`" . org-roam-buffer-toggle) ("C-" . org-roam-dailies-goto-next-note))) (org-roam-db-autosync-mode) #+END_SRC ***** Configure templates #+NAME::org-roam-templates #+BEGIN_SRC emacs-lisp (setq org-roam-dailies-capture-templates '(("d" "default" entry "* %?" :if-new (file+head "%<%Y-%m-%d>.org" (lambda () (concat ":PROPERTIES:\n:ID: " (org-id-new) "\n:END:\n" "#+TITLE: %<%Y-%m-%d>\n#+filetags: Daily \n" ; Added space here "* Log\n")))))) #+END_SRC ***** Extending Roam Here we add additional function to ~org-roam~ to either do something specific for more workflow, or otherwise make ~org-roam~ more full featured. ****** Set CREATED and LAST_MODIFIED filetags on save Sets timestamps in the Properties drawer of files. I intend to use this one day when rendering these notes as HTML, to quickly see files last touched. #+NAME::org-roam-set-timestamps-on-save #+BEGIN_SRC emacs-lisp (defvar dl/org-created-property-name "CREATED") (defun dl/org-set-created-property (&optional active name) (interactive) (let* ((created (or name dl/org-created-property-name)) (fmt (if active "<%s>" "[%s]")) (now (format fmt (format-time-string "%Y-%m-%d %a %H:%M")))) (unless (org-entry-get (point) created nil) (org-set-property created now) now))) (defun dl/org-find-time-file-property (property &optional anywhere) (save-excursion (goto-char (point-min)) (let ((first-heading (save-excursion (re-search-forward org-outline-regexp-bol nil t)))) (when (re-search-forward (format "^#\\+%s:" property) (if anywhere nil first-heading) t) (point))))) (defun dl/org-has-time-file-property-p (property &optional anywhere) (when-let ((pos (dl/org-find-time-file-property property anywhere))) (save-excursion (goto-char pos) (if (and (looking-at-p " ") (progn (forward-char) (org-at-timestamp-p 'lax))) pos -1)))) (defun dl/org-set-time-file-property (property &optional anywhere pos) (when-let ((pos (or pos (dl/org-find-time-file-property property)))) (save-excursion (goto-char pos) (if (looking-at-p " ") (forward-char) (insert " ")) (delete-region (point) (line-end-position)) (let* ((now (format-time-string "[%Y-%m-%d %a %H:%M]"))) (insert now))))) (defun dl/org-set-last-modified () "Update the LAST_MODIFIED file property in the preamble." (when (derived-mode-p 'org-mode) (dl/org-set-time-file-property "LAST_MODIFIED"))) #+END_SRC ****** Set CREATED on node creation #+NAME::org-roam-set-timestamps-on-save #+BEGIN_SRC emacs-lisp (defun dl/org-roam-create-id () "Add created date to org-roam node." (interactive) (org-id-get-create) (dl/org-set-created-property)) #+END_SRC *** UI improvements Anything related to improving org mode's appearance. **** Change color of ivy window selection #+NAME::ivy-window-selection #+BEGIN_SRC emacs-lisp (set-face-attribute 'ivy-current-match nil :foreground "#3d434d" :background "#ffcc66") #+END_SRC **** Change default bullets to be pretty Replaces the standard org-mode header asterisks with dots. #+NAME::org-mode-visuals #+BEGIN_SRC emacs-lisp (use-package org-superstar :after org :hook (org-mode . org-superstar-mode) :custom (org-superstar-remove-leading-stars t) (org-superstar-headline-bullets-list '("•" "•" "•" "◦" "◦" "◦" "◦"))) #+END_SRC **** Fonts #+NAME::org-mode-variable-width-fonts #+BEGIN_SRC emacs-lisp (add-hook 'org-mode-hook 'variable-pitch-mode) (require 'org-indent) (set-face-attribute 'org-block nil :foreground nil :inherit 'fixed-pitch) (set-face-attribute 'org-table nil :inherit 'fixed-pitch) (set-face-attribute 'org-formula nil :inherit 'fixed-pitch) (set-face-attribute 'org-code nil :inherit '(shadow fixed-pitch)) (set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch)) (set-face-attribute 'org-verbatim nil :inherit '(shadow fixed-pitch)) (set-face-attribute 'org-special-keyword nil :inherit '(font-lock-comment-face fixed-pitch)) (set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch)) (set-face-attribute 'org-checkbox nil :inherit 'fixed-pitch) (when (system-is-linux) (set-face-attribute 'org-document-title nil :font "Helvetica LT Std Condensed" :weight 'bold :height 1.2)) (when (system-is-mac) (set-face-attribute 'variable-pitch nil :font "Helvetica" :height 120)) (dolist (face '((org-level-1 . 1.2) (org-level-2 . 1.15) (org-level-3 . 1.1) (org-level-4 . 1.05) (org-level-5 . 1.05) (org-level-6 . 1.0) (org-level-7 . 1.0) (org-level-8 . 1.0))) (when (system-is-linux) (set-face-attribute (car face) nil :font "Helvetica LT Std Condensed" :weight 'medium :height (cdr face))) (when (system-is-mac) (set-face-attribute 'variable-pitch nil :font "Helvetica" :weight 'medium :height 170))) #+END_SRC ** Evil mode (aka Vim mode) *** Install package This is what makes emacs possible for me. All evil mode packages and related configuration. #+NAME: evil-packages #+BEGIN_SRC emacs-lisp (defun dl/evil-hook () (dolist (mode '(eshell-mode git-rebase-mode term-mode)) (add-to-list 'evil-emacs-state-modes mode))) ;; no evil mode for these modes (use-package evil :init (setq evil-want-integration t) ;; TODO: research what this does (setq evil-want-fine-undo 'fine) ;; undo/redo each motion (setq evil-want-Y-yank-to-eol t) ;; Y copies to end of line like vim (setq evil-want-C-u-scroll t) ;; vim like scroll up (evil-mode 1) :hook (evil-mode . dl/evil-hook) :config ;; Emacs "cancel" == vim "cancel" (define-key evil-insert-state-map (kbd "C-g") 'evil-normal-state) ;; Ctrl-h deletes in vim insert mode (define-key evil-insert-state-map (kbd "C-h") 'evil-delete-backward-char-and-join) ;; When we wrap lines, jump visually, not to the "actual" next line (evil-global-set-key 'motion "j" 'evil-next-visual-line) (evil-global-set-key 'motion "k" 'evil-previous-visual-line) (evil-set-initial-state 'message-buffer-mode 'normal) (evil-set-initial-state 'dashboard-mode 'normal)) ;; Gives me vim bindings elsewhere in emacs (use-package evil-collection :after evil :config (evil-collection-init)) ;; Keybindings in org mode (use-package evil-org :after evil :hook (org-mode . (lambda () evil-org-mode)) :config (require 'evil-org-agenda) (evil-org-agenda-set-keys)) ;; Branching undo system (use-package undo-tree :after evil :diminish :config (evil-set-undo-system 'undo-tree) (global-undo-tree-mode 1)) (use-package evil-commentary :after evil :config (evil-commentary-mode)) ;; Keep undo files from littering directories (setq undo-tree-history-directory-alist '(("." . "~/.local/state/emacs/undo"))) #+END_SRC ** Terminal #+NAME: vterm #+BEGIN_SRC emacs-lisp (use-package vterm :commands vterm :config (setq term-prompt-regexp "^[^#$%>\n]*[#$%>] *") (setq vterm-shell "zsh") (setq vterm-max-scrollback 10000)) #+END_SRC ** Managing files Configuration related to filesystem, either basic IO and interaction from emacs or directly moving files around where it makes sense. *** File browser ~dired~ provides a more visual interface to browsing files; similar to terminal programs like ~ranger~. #+BEGIN_SRC emacs-lisp (use-package all-the-icons-dired) (use-package dired :ensure nil :straight nil :defer 1 :commands (dired dired-jump) :config (setq dired-listing-switches "-agho --group-directories-first") (setq dired-omit-files "^\\.[^.].*") (setq dired-omit-verbose nil) (setq dired-hide-details-hide-symlink-targets nil) (put 'dired-find-alternate-file 'disabled nil) (setq delete-by-moving-to-trash t) (autoload 'dired-omit-mode "dired-x") (add-hook 'dired-load-hook (lambda () (interactive) (dired-collapse))) (add-hook 'dired-mode-hook (lambda () (interactive) (dired-omit-mode 1) (dired-hide-details-mode 1) (all-the-icons-dired-mode 1)) (hl-line-mode 1))) (use-package dired-single) (use-package dired-ranger) (use-package dired-collapse) (evil-collection-define-key 'normal 'dired-mode-map "h" 'dired-single-up-directory "c" 'find-file "H" 'dired-omit-mode "l" 'dired-single-buffer "y" 'dired-ranger-copy "X" 'dired-ranger-move "p" 'dired-ranger-paste) ;; Darwin needs ls from coreutils for dired to work (when (system-is-mac) (setq insert-directory-program (expand-file-name ".nix-profile/bin/ls" (getenv "HOME")))) #+END_SRC **** Quick shortcuts for common file tasks #+NAME::buffer-and-file-movement #+BEGIN_SRC emacs-lisp (defun my-org-archive-done-tasks () "Archive all DONE tasks in the current buffer." (interactive) (org-map-entries (lambda () (org-archive-subtree) (setq org-map-continue-from (outline-previous-heading))) "/DONE" 'tree)) (defun er-delete-file-and-buffer () "Kill the current buffer and deletes the file it is visiting." (interactive) (let ((filename (buffer-file-name))) (when filename (if (yes-or-no-p (concat "Do you really want to delete file: " filename "? ")) ; Ask for confirmation (if (vc-backend filename) (vc-delete-file filename) (progn (delete-file filename) (message "Deleted file %s" filename) (kill-buffer))) (message "Aborted"))))) ; Abort message (define-key org-mode-map (kbd "C-c D") 'my-org-archive-done-tasks) (define-key org-mode-map (kbd "C-c d") 'org-archive-subtree) (global-set-key (kbd "C-c x") #'er-delete-file-and-buffer) #+END_SRC *** Images Quickly work with images over drag-and-drop or the clipboard. [[https://github.com/abo-abo/org-download][Link to Project README]]. #+NAME: org-download-copy #+BEGIN_SRC emacs-lisp (use-package org-download) ;; Drag-and-drop to `dired` (add-hook 'dired-mode-hook 'org-download-enable) #+END_SRC *** Backups and auto-save These settings keep emacs from littering the filesystem with buffer backups. These files look like ~#yourfilename.txt#~ and would otherwise be dropped in your working directory. #+NAME: backup-files #+BEGIN_SRC emacs-lisp (setq backup-directory-alist `((".*" . "~/.local/state/emacs/backup")) backup-by-copying t ; Don't delink hardlinks version-control t ; Use version numbers on backups delete-old-versions t) ; Automatically delete excess backups #+END_SRC #+NAME: local-file-transforms #+BEGIN_SRC emacs-lisp (setq auto-save-file-name-transforms `((".*" "~/.local/state/emacs/" t))) (setq lock-file-name-transforms `((".*" "~/.local/state/emacs/lock-files/" t))) #+END_SRC ** Managing projects *** Projectile Projectile enables me to organize projects with a killer grep interface. #+NAME: projectile #+BEGIN_SRC emacs-lisp (use-package ripgrep) (use-package projectile :diminish projectile-mode :config (projectile-mode) :custom ((projectile-completion-system 'ivy)) :bind-keymap ("C-c p" . projectile-command-map) :init (setq projectile-enable-caching t) (setq projectile-sort-order 'recently-active) (setq projectile-switch-project-action #'projectile-dired)) (setq projectile-project-root-files-bottom-up '("package.json" ".projectile" ".project" ".git")) (setq projectile-ignored-projects '("~/.emacs.d/")) (setq projectile-globally-ignored-directories '("dist" "node_modules" ".log" ".git")) ;; Gives me Ivy options in the Projectile menus (use-package counsel-projectile :after projectile) #+END_SRC ** Writing *** Modes Experimenting with different distraction free writing modes. #+BEGIN_SRC emacs-lisp (defun enter-writing-mode () (load-theme 'doom-one-light t) (when (bound-and-true-p treemacs-mode) (treemacs)) (add-hook 'window-buffer-change-functions 'check-leaving-buffer nil t)) (defun exit-writing-mode () (load-theme 'doom-one t) (when (bound-and-true-p treemacs-mode) (treemacs)) (remove-hook 'window-buffer-change-functions 'check-leaving-buffer t)) (add-hook 'writeroom-mode-hook (lambda () (if writeroom-mode (enter-writing-mode) (exit-writing-mode)))) (use-package writeroom-mode :ensure t) (global-set-key (kbd "C-c w") 'writeroom-mode) #+END_SRC *** Spell Check / Flycheck Mode Everything related to spell and grammar checking. #+NAME: spell-check #+BEGIN_SRC emacs-lisp (when (system-is-mac) (with-eval-after-load "ispell" (setq ispell-program-name (expand-file-name ".nix-profile/bin/hunspell" (getenv "HOME"))) (setq ispell-dictionary "en_US"))) (use-package flyspell-correct :after flyspell :bind (:map flyspell-mode-map ("C-;" . flyspell-correct-wrapper))) (use-package flyspell-correct-ivy :after flyspell-correct) (add-hook 'git-commit-mode-hook 'turn-on-flyspell) (add-hook 'text-mode-hook 'flyspell-mode) ;; Disable this for now, doesn't play well with long literate configuration ;; (add-hook 'org-mode-hook 'flyspell-mode) (add-hook 'prog-mode-hook 'flyspell-prog-mode) (defun spell() (interactive) (flyspell-mode 1)) #+END_SRC ** Coding *** Compile buffers Everything related to M-x compile. #+NAME: compilation-buffer #+BEGIN_SRC emacs-lisp ;; Auto scroll the buffer as we compile (setq compilation-scroll-output t) ;; By default, eshell doesn't support ANSI colors. Enable them for compilation. (require 'ansi-color) (defun colorize-compilation-buffer () (let ((inhibit-read-only t)) (ansi-color-apply-on-region (point-min) (point-max)))) (add-hook 'compilation-filter-hook 'colorize-compilation-buffer) #+END_SRC *** Tide #+NAME: tide-mode #+BEGIN_SRC emacs-lisp (use-package tide :ensure t :after (typescript-mode company flycheck) :hook ((typescript-mode . tide-setup) (typescript-mode . tide-hl-identifier-mode) (before-save . tide-format-before-save))) (setq tide-format-options '(:insertSpaceAfterFunctionKeywordForAnonymousFunctions t :placeOpenBraceOnNewLineForFunctions nil)) #+END_SRC *** LSP This is my IDE. It includes the same engine that powers VS Code, in addition to Github Copilot. #+NAME: lsp-mode #+BEGIN_SRC emacs-lisp (use-package lsp-mode :commands lsp lsp-deferred :init (setq lsp-keymap-prefix "C-c l") ;;(setq lsp-keep-workspace-alive nil) ;;(setq lsp-restart 'ignore) (setq lsp-headerline-breadcrumb-enable nil) (setq lsp-auto-guess-root t) (setq lsp-enable-which-key-integration t)) (use-package lsp-ui :hook (lsp-mode . lsp-ui-mode) :custom (lsp-ui-doc-position 'bottom)) (use-package lsp-treemacs :after lsp) (use-package company :after lsp-mode :hook (lsp-mode . company-mode) :bind (:map company-active-map ("" . company-complete-selection)) (:map lsp-mode-map ("" . company-indent-or-complete-common)) :custom (company-minimum-prefix-length 1) (company-idle-delay 0.0)) (use-package company-box :hook (company-mode . company-box-mode)) (add-hook 'lsp-mode-hook #'lsp-headerline-breadcrumb-mode) #+END_SRC **** Shortcuts Leader keys for lsp-mode. #+NAME: lsp-leader-keys #+BEGIN_SRC emacs-lisp (defun dl/lsp-find-references-other-window () (interactive) (switch-to-buffer-other-window (current-buffer)) (lsp-find-references)) (defun dl/lsp-find-implementation-other-window () (interactive) (switch-to-buffer-other-window (current-buffer)) (lsp-find-implementation)) (defun dl/lsp-find-definition-other-window () (interactive) (switch-to-buffer-other-window (current-buffer)) (lsp-find-definition)) (dl/leader-keys "l" '(:ignore t :which-key "lsp") "lf" '(dl/lsp-find-references-other-window :which-key "find references") "lc" '(dl/lsp-find-implementation-other-window :which-key "find implementation") "ls" '(lsp-treemacs-symbols :which-key "list symbols") "lt" '(list-flycheck-errors :which-key "list errors") "lh" '(lsp-treemacs-call-hierarchy :which-key "call hierarchy") "lF" '(lsp-format-buffer :which-key "format buffer") "li" '(lsp-organize-imports :which-key "organize imports") "ll" '(lsp :which-key "enable lsp mode") "lr" '(lsp-rename :which-key "rename") "ld" '(dl/lsp-find-definition-other-window :which-key "goto definition")) #+END_SRC *** Languages **** Python #+NAME: python #+BEGIN_SRC emacs-lisp (use-package lsp-pyright :ensure t :hook (python-mode . (lambda () (require 'lsp-pyright) (lsp-deferred)))) ; or lsp-deferred #+END_SRC **** Shell scripts #+NAME: shell-scripts #+BEGIN_SRC emacs-lisp (add-to-list 'auto-mode-alist '("\\.env" . shell-script-mode)) #+END_SRC **** YAML #+NAME: yaml-mode #+BEGIN_SRC emacs-lisp (use-package yaml-mode :commands (markdown-mode gfm-mode) :mode (("\\.yml\\'" . yaml-mode))) #+END_SRC **** Markdown #+NAME: markdown-mode #+BEGIN_SRC emacs-lisp ;; This uses Github Flavored Markdown for README files (use-package markdown-mode :commands (markdown-mode gfm-mode) :mode (("README\\.md\\'" . gfm-mode) ("\\.md\\'" . markdown-mode) ("\\.markdown\\'" . markdown-mode)) :init (setq markdown-command "pandoc")) #+END_SRC **** HTML ***** Web mode Emmet mode gives autocompletion for HTML tags using short hand notations, which for I use the TAB key. #+NAME: html-auto-completion #+BEGIN_SRC emacs-lisp (use-package emmet-mode) (add-hook 'sgml-mode-hook 'emmet-mode) (add-hook 'css-mode-hook 'emmet-mode) (define-key emmet-mode-keymap [tab] 'emmet-expand-line) (add-to-list 'emmet-jsx-major-modes 'jsx-mode) #+END_SRC ***** Rainbow mode Rainbow mode is an Emacs minor mode to highlight the color shown by a RGB hex triplet (example #FFFFFF). #+NAME: rainbow-mode #+BEGIN_SRC emacs-lisp (use-package rainbow-mode) #+END_SRC **** golang #+NAME: golang-config #+BEGIN_SRC emacs-lisp (use-package go-mode) (use-package company-go) ;; Set up before-save hooks to format buffer and add/delete imports. ;; Make sure you don't have other gofmt/goimports hooks enabled. (defun lsp-go-install-save-hooks () (add-hook 'before-save-hook #'lsp-format-buffer t t) (add-hook 'before-save-hook #'lsp-organize-imports t t)) (add-hook 'go-mode-hook #'lsp-go-install-save-hooks) (add-hook 'go-mode-hook #'lsp-deferred) (defun dl/go-mode-hook () ; Call Gofmt before saving (add-hook 'before-save-hook 'gofmt-before-save) ; Customize compile command to run go build (if (not (string-match "go" compile-command)) (set (make-local-variable 'compile-command) "go build -v && go test -v && go vet")) ; Godef jump key binding (local-set-key (kbd "M-.") 'godef-jump) ;; pop-tag-mark moves back before jump, to undo M-, (local-set-key (kbd "M-*") 'pop-tag-mark)) (add-hook 'go-mode-hook 'dl/go-mode-hook) #+END_SRC **** Javascript / Typescript #+NAME: javascript #+BEGIN_SRC emacs-lisp (use-package pnpm-mode) (use-package prisma-mode :straight (:host github :repo "pimeys/emacs-prisma-mode" :branch "main")) (use-package web-mode :hook (web-mode . lsp-deferred)) (add-to-list 'auto-mode-alist '("\\.jsx?$" . web-mode)) (add-to-list 'auto-mode-alist '("\\.tsx$" . web-mode)) (add-to-list 'auto-mode-alist '("\\.ts$" . web-mode)) (add-to-list 'auto-mode-alist '("\\.js$" . web-mode)) (add-to-list 'auto-mode-alist '("\\.html$" . web-mode)) (add-to-list 'auto-mode-alist '("\\.vue\\'" . web-mode)) (defun web-mode-init-hook () "Hooks for Web mode. Adjust indent." (setq web-mode-markup-indent-offset 2)) (add-hook 'web-mode-hook 'web-mode-init-hook) ;; Vue.js / Nuxt.js Language Server (straight-use-package '(lsp-volar :type git :host github :repo "jadestrong/lsp-volar")) (add-hook 'typescript-mode-hook #'lsp-deferred) ;; Keeps indentation organized across these modes (use-package prettier-js) (add-hook 'js2-mode-hook 'prettier-js-mode) (add-hook 'web-mode-hook 'prettier-js-mode) (add-hook 'css-mode-hook 'prettier-js-mode) #+END_SRC *** Git #+NAME: magit-git #+BEGIN_SRC emacs-lisp (use-package magit :commands (magit-status magit-get-current-branch)) (define-key magit-hunk-section-map (kbd "RET") 'magit-diff-visit-file-other-window) #+END_SRC *** Infrastructure **** Nix Nix is my package manager and operating system of choice; this mode enables me to have a better time writing Nix expressions. #+NAME: nix-mode #+begin_src emacs-lisp (use-package nix-mode :mode "\\.nix\\'") #+end_src **** Docker mode #+NAME: dockerfile-mode #+BEGIN_SRC emacs-lisp ;; This uses dockerfile-mode for Docker files (use-package dockerfile-mode) (put 'dockerfile-image-name 'safe-local-variable #'stringp) (add-to-list 'auto-mode-alist '("\\Dockerfile?$" . dockerfile-mode)) ;; auto-enable for Dockerfiles #+END_SRC **** Terraform #+NAME: terraform-mode #+BEGIN_SRC emacs-lisp (use-package terraform-mode :hook ((terraform-mode . lsp-deferred) (terraform-mode . terraform-format-on-save-mode))) (add-to-list 'auto-mode-alist '("\\.tf\\'" . terraform-mode)) #+END_SRC ** AI *** Copilot #+BEGIN_SRC emacs-lisp (use-package copilot :straight (:host github :repo "zerolfx/copilot.el" :files ("dist" "*.el")) :ensure t) (add-hook 'prog-mode-hook 'copilot-mode) (define-key copilot-completion-map (kbd "") 'copilot-accept-completion) (define-key copilot-completion-map (kbd "TAB") 'copilot-accept-completion) #+END_SRC ** Learning Emacs These packages may come and go, but ultimately aid in my understanding of emacs and emacs lisp. *** org-babel **** Load languages to run in org mode code blocks #+BEGIN_SRC emacs-lisp (with-eval-after-load 'org (org-babel-do-load-languages 'org-babel-load-languages '( (emacs-lisp . t) (python . t) (sql . t) (shell . t))) ) #+END_SRC **** ANSI color codes in org babel shell output Found [[https://emacs.stackexchange.com/questions/44664/apply-ansi-color-escape-sequences-for-org-babel-results][here]]. #+BEGIN_SRC emacs-lisp (defun dl/babel-ansi () (when-let ((beg (org-babel-where-is-src-block-result nil nil))) (save-excursion (goto-char beg) (when (looking-at org-babel-result-regexp) (let ((end (org-babel-result-end)) (ansi-color-context-region nil)) (ansi-color-apply-on-region beg end)))))) (add-hook 'org-babel-after-execute-hook 'dl/babel-ansi) #+END_SRC *** Show real-time key bindings in a separate buffer #+NAME: command-log #+BEGIN_SRC emacs-lisp ;; Gives me a fancy list of commands I run (use-package command-log-mode) (setq global-command-log-mode t) #+END_SRC *** Panel popup to show key bindings #+NAME: which-key #+BEGIN_SRC emacs-lisp ;; Gives me a fancy list of commands I run (use-package which-key :init (which-key-mode) :diminish which-key-mode :config (setq which-key-idle-delay 0.3)) #+END_SRC *** Helpful documentation strings for common functions #+NAME: helpful #+BEGIN_SRC emacs-lisp (use-package helpful :custom ;; Remap Counsel help functions (counsel-describe-function-function #'helpful-callable) (counsel-describe-variable-function #'helpful-variable) :bind ;; Remap default help functions ([remap describe-function] . helpful-function) ([remap describe-symbol] . helpful-symbol) ([remap describe-variable] . helpful-variable) ([remap describe-command] . helpful-command) ([remap describe-key] . helpful-key)) #+END_SRC