48 KiB
Emacs
- Configuration
- About my Emacs
- Personal Information
- Initialization
- Keybindings
- Display options
- Global Settings
- Org mode
- Evil mode (aka Vim mode)
- Terminal
- Managing files
- Managing projects
- Writing
- Coding
- AI
- Learning Emacs
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; just a few lines in my Nix config. 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 here.
Each block of code below is the actual Emacs configuration. Formally, this style of configuration is named "literate programming".
Personal Information
Just me!
(setq user-full-name "Jip J. Dekker"
user-mail-address "jip@dekker.one")
Initialization
Booting up
Mainly splash screen settings. In the future we may look to optimize performance here.
;; 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)))
Add package sources
This associates our package manager with the right source (MELPA).
(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))
System Definitions
Conditionals
(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)))
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.
(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))
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, here is a good explanation on leader keys.
;; ESC will also cancel/quit/etc.
(global-set-key (kbd "<escape>") '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 ","))
Emacs cleanup
Helpful keybindings to help keep Emacs sane.
(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)
Treemacs
(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"))
Toggles
Various UI related toggles.
(dl/leader-keys
"h" '(counsel-load-theme :which-key "choose theme"))
Rotate windows
Various helpers and packages I find useful for window management.
;; 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"))
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.
;; 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
Set mode margins
This is used primarily to center org mode text.
(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))
Don't blink the cursor
(blink-cursor-mode -1)
Colors
Rainbow delimiters
Makes my lisp parens pretty, and easy to spot.
(use-package rainbow-delimiters
:hook (prog-mode . rainbow-delimiters-mode))
Color definitions
Define a global set of colors to be used everywhere in Emacs.
(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")
Addons
"Powerline"
Keeps info at my fingertips. Modeline is much better than Vim's Powerline (sorry Vim).
;; 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))
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.
(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)
Easy window motions with ace-window
Predefine windows with hotkeys and jump to them.
;; 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))
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
;; 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))
Dashboard
(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)
Keybindings
Spaces over tabs
We use two spaces in place of tabs. I don't even want to hear it.
(setq-default indent-tabs-mode nil
js-indent-level 2
tab-width 2)
(setq-default evil-shift-width 2)
Buffers
(global-set-key (kbd "<C-tab>") 'next-buffer)
Display options
Themes
Doom Emacs
(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))
Global Settings
Global Modes
I like these modes, what can I say. They're good to me.
(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)
Org mode
Agenda
Initialize org-agenda file and set some key bindings to create tasks. #+NAME::org-mode-agenda
(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)
Set org faces
Set various types and colors for org-mode
.
#+NAME::org-mode-faces
;; 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))))
Format org-agenda views
This block sets the org-agenda-prefix-format
in an friendly way for org-roam
(credit to this post). It truncates long filenames and removes the org-roam
timestamp slug.
#+NAME::org-agenda-prefixes
(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"))
org-super-agenda views
Setup for org-super-agenda
and org-ql
.
#+NAME::org-super-agenda
(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)
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
(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)))))
Install package
If you haven't heard of org mode, go watch this talk and come back when you are finished.
Leader key shortcuts
#+NAME::org-mode-quick-entry
(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"))
Roam capture templates
These are templates used to create new notes.
#+NAME::roam-templates
(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)))
Org Roam
Install package
#+NAME::org-roam-package
(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-<left>" . org-roam-dailies-goto-previous-note)
("C-`" . org-roam-buffer-toggle)
("C-<right>" . org-roam-dailies-goto-next-note)))
(org-roam-db-autosync-mode)
Configure templates
#+NAME::org-roam-templates
(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"))))))
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.
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
(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")))
#+NAME::org-roam-set-timestamps-on-save
(defun dl/org-roam-create-id ()
"Add created date to org-roam node."
(interactive)
(org-id-get-create)
(dl/org-set-created-property))
UI improvements
Anything related to improving org mode's appearance.
Change color of ivy window selection
#+NAME::ivy-window-selection
(set-face-attribute 'ivy-current-match nil :foreground "#3d434d" :background "#ffcc66")
Change default bullets to be pretty
Replaces the standard org-mode header asterisks with dots. #+NAME::org-mode-visuals
(use-package org-superstar
:after org
:hook (org-mode . org-superstar-mode)
:custom
(org-superstar-remove-leading-stars t)
(org-superstar-headline-bullets-list '("•" "•" "•" "◦" "◦" "◦" "◦")))
Fonts
#+NAME::org-mode-variable-width-fonts
(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)))
Evil mode (aka Vim mode)
Install package
This is what makes emacs possible for me. All evil mode packages and related configuration.
(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")))
Terminal
(use-package vterm
:commands vterm
:config
(setq term-prompt-regexp "^[^#$%>\n]*[#$%>] *")
(setq vterm-shell "zsh")
(setq vterm-max-scrollback 10000))
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
.
(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"))))
Quick shortcuts for common file tasks
#+NAME::buffer-and-file-movement
(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)
Images
Quickly work with images over drag-and-drop or the clipboard. Link to Project README.
(use-package org-download)
;; Drag-and-drop to `dired`
(add-hook 'dired-mode-hook 'org-download-enable)
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.
(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
(setq auto-save-file-name-transforms
`((".*" "~/.local/state/emacs/" t)))
(setq lock-file-name-transforms
`((".*" "~/.local/state/emacs/lock-files/" t)))
Managing projects
Projectile
Projectile enables me to organize projects with a killer grep interface.
(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)
Writing
Modes
Experimenting with different distraction free writing modes.
(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)
Spell Check / Flycheck Mode
Everything related to spell and grammar checking.
(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))
Coding
Compile buffers
Everything related to M-x compile.
;; 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)
Tide
(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))
LSP
This is my IDE. It includes the same engine that powers VS Code, in addition to Github Copilot.
(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
("<tab>" . company-complete-selection))
(:map lsp-mode-map
("<tab>" . 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)
Shortcuts
Leader keys for lsp-mode.
(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"))
Languages
Python
(use-package lsp-pyright
:ensure t
:hook (python-mode . (lambda ()
(require 'lsp-pyright)
(lsp-deferred)))) ; or lsp-deferred
Shell scripts
(add-to-list 'auto-mode-alist '("\\.env" . shell-script-mode))
YAML
(use-package yaml-mode
:commands (markdown-mode gfm-mode)
:mode (("\\.yml\\'" . yaml-mode)))
Markdown
;; 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"))
HTML
Web mode
Emmet mode gives autocompletion for HTML tags using short hand notations, which for I use the TAB key.
(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)
Rainbow mode
Rainbow mode is an Emacs minor mode to highlight the color shown by a RGB hex triplet (example #FFFFFF).
(use-package rainbow-mode)
golang
(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)
Javascript / Typescript
(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)
Git
(use-package magit
:commands (magit-status magit-get-current-branch))
(define-key magit-hunk-section-map (kbd "RET") 'magit-diff-visit-file-other-window)
Infrastructure
Nix
Nix is my package manager and operating system of choice; this mode enables me to have a better time writing Nix expressions.
(use-package nix-mode
:mode "\\.nix\\'")
Docker mode
;; 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
Terraform
(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))
AI
Copilot
(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 "<tab>") 'copilot-accept-completion)
(define-key copilot-completion-map (kbd "TAB") 'copilot-accept-completion)
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
(with-eval-after-load 'org
(org-babel-do-load-languages
'org-babel-load-languages
'(
(emacs-lisp . t)
(python . t)
(sql . t)
(shell . t)))
)
ANSI color codes in org babel shell output
Found here.
(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)
Show real-time key bindings in a separate buffer
;; Gives me a fancy list of commands I run
(use-package command-log-mode)
(setq global-command-log-mode t)
Panel popup to show key bindings
;; 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))
Helpful documentation strings for common functions
(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))