1
0
Jip J. Dekker 4363d1a7d4 Initial Commit
Add flake template from dustinlyons/nixos-config
2023-12-11 17:35:39 +11:00

1478 lines
48 KiB
Org Mode

#+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 "<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 ","))
#+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 "<C-tab>") '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-<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)
#+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
("<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)
#+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 "<tab>") '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