Initialize skip-dev-config
(if necessary).
This variable indicates whether to skip sections focused on
installing developpment-focused packages.
; initialize skip-dev-config if not defined
(if (not (boundp 'skip-dev-config))
(setq skip-dev-config nil))
Uncomment to profile loading profile.
;(require 'profiler)
;(profiler-start 'cpu)
Using straight.el and use-package.
See this article for benefits of using straight.el.
(message "bootstrapping straight.el")
(defvar bootstrap-version)
(let ((bootstrap-file
(expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
(bootstrap-version 6))
(unless (file-exists-p bootstrap-file)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
'silent 'inhibit-cookies)
(goto-char (point-max))
(eval-print-last-sexp)))
(load bootstrap-file nil 'nomessage))
(setq package-enable-at-startup nil)
use-package allows for you to install and configure a package in one step.
(straight-use-package 'use-package)
(message "load straight")
(use-package straight
:custom (straight-use-package-by-default t))
Now in order to install a package, all we need to do is:
; (use-package evil-commentary)
There are options for configuring packages with straight.el, too:
:init
- code that will be run before installing the package:config
- code that will be run right after the package is installed:bind
- adds key bindings after a module has been installed:custom:
- set customizable variables
See the straight.el getting started guide for more documentation on how to load and configure packages with straight.el.
Add Milkypostman’s Emacs Lisp Package Archive (MELPA) as a package archive.
This was first introduced in order to install Org-roam.
(require 'package)
(add-to-list 'package-archives
'("melpa" . "http://melpa.org/packages/") t)
Disable GUI elements. Will rely on key bindings.
(scroll-bar-mode -1)
(tool-bar-mode -1)
(menu-bar-mode -1)
Never, ever use tabs.
(setq-default indent-tabs-mode nil)
It’s easy to lose track of where the cursor is. Globally enable line highlighting.
Continue to highlight a line even when focus has moved to another window.
(global-hl-line-mode 1)
(setq global-hl-line-sticky-flag t)
Otherwise, don’t notice this until it’s time to commit changes.
(setq-default show-trailing-whitespace t)
(show-paren-mode 1)
(setq initial-scratch-message nil)
(setq confirm-kill-emacs 'y-or-n-p)
(setq inhibit-startup-message t)
(setq initial-scratch-message "")
(fset 'yes-or-no-p 'y-or-n-p)
Allow narrowing to a region (C-x n n
)
(put 'narrow-to-region 'disabled nil)
M-x
is frequently used, but not as convenient to enter as a Control command.
Globally replace M-x
with C-m
.
(keyboard-translate ?\C-m ?\M-x)
I need to kill buffers all. the. time. Let’s make it easier.
(global-set-key (kbd "C-k") #'kill-current-buffer)
I also need to kill windows all the time. Let’s make that easier, too.
(global-set-key (kbd "C-.") #'delete-window)
Enabled Dired Omit Mode to hide uninteresting files. Update regex filter used to omit files so that Emacs backup files are excluded, too.
(setq dired-omit-files "\\`[.]?#\\|~$\\|__pycache__\\|.swp\\|.pytest_cache")
(add-hook 'dired-mode-hook (lambda ()
(dired-omit-mode)))
org-mode code blocks may seem to have improper indenting. Remember to use C-c ’ to get proper indenting.
Create recommended keybindings for org-mode.
(global-set-key (kbd "C-c l") #'org-store-link)
(global-set-key (kbd "C-c a") #'org-agenda)
Add support for TODO, DOING, and DONE states when working with TODO items.
(setq org-todo-keywords
'((sequence
;; open items
"TODO"
"DOING"
"|" ; entries after pipe are considered completed in [%] and [/]
;; closed items
"DONE"
"BLOCKED"
)))
(setq org-todo-keyword-faces
'(
("TODO" . "light pink")
("DOING" . "yellow")
("DONE" . "light green")
("BLOCKED" . "red")
))
Include org files in agenda if they are in \~/org/agenda
.
(setq org-directory (expand-file-name "~/org"))
(let ((agenda-dir (expand-file-name "agenda" org-directory)))
(setq org-agenda-files (list agenda-dir))
(setq org-default-notes-file (expand-file-name "notes.org" agenda-dir)))
Enable auto-fill-mode for org-mode.
Set fill-column
to 80
.
(setq fill-column 80)
(add-hook 'org-mode-hook 'turn-on-auto-fill)
Add support for links that open PDFs to a given page. (Retrieved from this emacs.stackexchange.com answer on 2023-08-11).
(defun org-pdf-open (link)
"Where page number is 105, the link should look like:
[[pdf:/path/to/file.pdf#page=105][My description.]]"
(let* ((path+page (split-string link "#page="))
(pdf-file (car path+page))
(page (car (cdr path+page))))
(start-process "view-pdf" nil "evince" "--page-index" page pdf-file)))
(org-add-link-type "pdf" 'org-pdf-open nil)
Install org-download.
Instruct org to always display inline images.
Configure org-download to store images in an images
directory
located in the current directory of the Org file.
Finally, instruct org-download to not use the org-mode heading to help organize images on the file system (e.g. do not create a sub-directory with the current heading’s name).
Bind org-download-clipboard
– which “pastes” the contents of the clipboard into the current org file –
to M-g
.
; Ensure org-pictures directory exists
(message "load org-download")
(use-package org-download
:config
(add-hook 'dired-mode-hook 'org-download-enable)
:custom
(org-startup-with-inline-images t)
(org-download-image-dir "images")
(org-download-heading-lvl nil)
:bind
("M-g" . org-download-clipboard))
See this page for information on how to get started with evil mode.
In the config
section, set evil-want-C-i-jump
to nil
since C-i
is tab and we want to preserve tab’s default behavior.
(If we don’t set this to nil
, tab will invoke evil-jump-forward
instead of org-cycle
in org-mode, for example,
preventing us from cycling through the different folding options for a node).
For some reason, the above approach works in Debian, but not Mac OSX.
Taking things a step further, we also use with-eval-after-load
to forcefully unset tab in evil-motion-state-map
.
(Found this approach here.)
(message "load evil")
(use-package evil
:config
(evil-mode)
(evil-set-initial-state 'help-mode 'emacs)
(evil-set-initial-state 'Info-mode 'emacs)
(evil-set-initial-state 'ivy-occur-mode 'emacs)
(evil-set-undo-system 'undo-tree)
(setq evil-want-C-i-jump nil)
(with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "TAB") nil))
(with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "C-b") 'org-roam-node-find))
(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "C-r") 'org-roam-capture))
(with-eval-after-load 'evil-maps (define-key evil-motion-state-map (kbd "C-d") 'avy-goto-char-timer))
(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "C-p") 'projectile-command-map))
(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "C-.") 'delete-window)))
We can’t go anywhere without Magit!
Include a hook that drops the user into emacs mode when prompted for a Git commit message.
(message "load magit")
(use-package magit
:config
(add-hook 'git-commit-mode-hook 'evil-emacs-state))
Make the Magit status window the only window in view when it opens.
(defun jl/magit-status ()
"Open magit-status window by itself"
(interactive)
(magit-status)
(delete-other-windows))
(define-key (current-global-map) [remap magit-status] 'jl/magit-status)
browse-at-remote opens the GitHub page corresponding to current location in buffer.
(use-package browse-at-remote)
(global-set-key (kbd "C-c g g") 'browse-at-remote)
; When working with Enterprise GitHub, let browse-at-remote
; know that the remote represents a Git Hub repository by running:
; > git config --add browseAtRemote.type "github"
; If all remotes use github, you can apply this setting globally with:
; > git config --global --add browseAtRemote.type "github"
Install Org-roam.
Be aware that Org-roam tends to assume that newer versions of Emacs packages are installed (e.g. org-mode, magit).
(message "load org-roam")
(use-package org-roam
:ensure t
:bind (("C-c h" . (lambda () (interactive) (call-interactively 'org-roam-buffer-toggle) (other-window 1)))
("C-c i" . org-roam-node-insert)
("C-c u" . org-roam-dailies-goto-today)
("C-c y" . org-roam-dailies-goto-yesterday)
("C-c n" . jl/org-roam-goto-week-by-number))
:config
(setq org-roam-completion-everywhere t))
Create and configure default Org-roam directory.
(make-directory "~/org-roam" t)
(setq org-roam-directory (file-truename "~/org-roam"))
This may take some time during the first run. Subsequent runs should be much faster, as they will only process modified files.
Can call M-x org-roam-db-sync
interactively to re-index.
(org-roam-db-autosync-mode)
(setq org-roam-capture-templates
'(("d" "default" plain "%?"
:target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}
")
:unnarrowed t)
("c" "common template" plain "* Overview
\* Reference
\* Links
%?
\* History
\* See Also
" :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}
")
:unnarrowed t)
("h" "history note" plain "** %<%Y-%m-%d %H:%M> %?"
:target (file+head+olp "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}"
("History"))
:empty-lines 1)
("y" "year note" plain "* %<%Y>"
:target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}
"))))
(global-set-key (kbd "C-r") #'org-roam-capture)
Install YASnippet.
Snippet examples available here.
Walkthrough of using snippets available here.
(message "load yasnippet")
(use-package yasnippet
:config
(yas-global-mode)
(setq yas-indent-line 'fixed))
Install yaml-mode.
More information on yaml-mode is available here.
(message "load yaml-mode")
(use-package yaml-mode
:config
(add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode)))
Perspective offers the ability to:
- Create (named) window layouts, refered to as perspectives
- Save perspectives to disk
- Only list buffers used by current perspective
Key Perspective commands are outlined here.
The Perspective prefix key is set to C-c
C-z
below.
(message "load perspective")
(use-package perspective
:custom
(persp-mode-prefix-key (kbd "C-c C-z"))
:init
(persp-mode)
:config
(setq persp-state-default-file "~/.emacs.d/persp-"))
Projectile offers several commands for interacting with files within the scope of a project.
Map projectile-find-file
to C-f
in the evil-normal-state-map
because of how frequently this gets called.
We map this in evil-normal-state-map
specifically so that C-f
is not shadowed in other modes
where it is less likely to be used anyways.
(message "load projectile")
(use-package projectile
:init
(setq projectile-project-search-path
'("~/git/" "~/org/" "~/.emacs.d"
("~/johnny-bookkeeper" . 5)
("~/johnny-do-your-work" . 5)
("~/johnny-inventory" . 5)
("~/johnny-jim" . 5)
("~/johnny-shared" . 5)
("~/johnny-steph" . 5)))
(projectile-mode)
(with-eval-after-load 'evil-maps (define-key evil-normal-state-map (kbd "C-f") 'projectile-find-file))
:bind
(:map projectile-command-map
("s s" . (lambda () (interactive) (call-interactively 'projectile-ag) (other-window 1)))))
Install the ag package as well so that projectile can make ag searches.
Map projectile-ag
to C-n
in the evil-normal-state-map
because of how frequently this gets called.
We map this in evil-normal-state-map
specifically so that C-n
is not shadowed in other modes
where it is less likely to be used anyways.
As a convenience, wrap projectile-ag
in a lambda function
that automatically switches us over to the other window.
(Did not find any way to configure this behavior
using projectile variables or function arguments).
(message "load ag")
(use-package ag
:init
(with-eval-after-load 'evil-maps
(define-key evil-normal-state-map (kbd "C-n")
(lambda () (interactive)
(call-interactively 'projectile-ag)
(other-window 1))))
:config
(add-hook 'ag-mode-hook 'evil-emacs-state))
Create a custom initial project view
that will be used when invoking projectile-switch-project
.
(defun open-last-modified-scm-file ()
"Determine most recently modified file according to Git"
(interactive)
(let ((my-output-buffer (generate-new-buffer "*git-last-modified-file*"))
(my-project-directory (cdr (project-current))))
(call-process "git" nil my-output-buffer nil "-C" my-project-directory "log" "-1" "--name-only" "--format=oneline" "--no-merges")
(save-excursion
(with-current-buffer my-output-buffer
(goto-char (point-max))
(join-line)
(setq my-last-modified-scm-file (concat my-project-directory (buffer-substring (point-at-bol) (point-at-eol))))
(kill-buffer my-output-buffer))))
(find-file my-last-modified-scm-file))
(defun jl/default-project-view ()
""
(interactive)
(if (one-window-p)
; if only one window is open, proceed with opening a full project workspace
(progn
; clear all other windows
(delete-other-windows)
; in first window, show directory
(let* ((project-abs-path (cdr (project-current)))
(project-name (file-name-nondirectory (directory-file-name
(expand-file-name project-abs-path)))))
(dired (cdr (project-current)))
(open-last-modified-scm-file)
; in next window, show terminal
(split-window-right)
(other-window 1)
(call-interactively 'magit-fetch-all)
(projectile-run-vterm)))
; otherwise, open dired, pointing at the root directory of the project
(dired (cdr (project-current)))))
(setq projectile-switch-project-action #'jl/default-project-view)
Avy provides an efficient, character / tree-based approach to jumping to a line or matching substring.
(message "load avy")
(use-package avy
:config
(global-set-key (kbd "C-l") 'avy-goto-line))
Collapse a series of keybindings into single keystrokes using Hydra.
Note that the :color key has a special meaning with hydras; red hydra heads do not exit, whereas blue hydra heads exit after executing their action.
In the snippet below, all heads are red by default,
but the C-w
head is marked as blue.
So to exit the hydra, the user can press C-w
.
(message "load hydra")
(use-package hydra)
; From https://blog.genenakagaki.com/en/my-emacs-life-is-better-with-hydra.html
(defhydra hydra-window (global-map "C-c w" :color red)
"
| Navigation^^ | Placement^^ | Create, Delete^^ | Adjustment^^ |
|^^-----------------+^^-------------------+^^-------------------------+^^--------------------|
| _h_: go left | _H_: move to left | _v_: split vertically | _=_: balance windows |
| _j_: go down | _J_: move to bottom | _s_: split horizontally | _+_: increase height |
| _k_: go up | _K_: move to top | _q_: delete window | _-_: decrease height |
| _l_: go right | _L_: move to right | _Q_: delete other windows | _>_: increase width |
| _w_: go to next | ^^ | ^^ | _<_: decrease width |
| _C-w_: go to next | ^^ | ^^ | ^^ |
"
("+" evil-window-increase-height)
("-" evil-window-decrease-height)
("<" evil-window-decrease-width)
(">" evil-window-increase-width)
("=" balance-windows)
("C-w" evil-window-next nil :color blue)
("H" evil-window-move-far-left)
("J" evil-window-move-very-bottom)
("K" evil-window-move-very-top)
("L" evil-window-move-far-right)
("h" evil-window-left)
("j" evil-window-down)
("k" evil-window-up)
("l" evil-window-right)
("q" evil-window-delete)
("Q" delete-other-windows)
("s" evil-window-split)
("v" evil-window-vsplit)
("w" evil-window-next))
While we won’t enable flycheck globally (via (global-flycheck-mode)
),
we don’t want the ability to check syntax across various modes.
Flycheck - a replacement for Flymake - should do the trick.
The quickstart guide for Flycheck is available here.
(message "load flycheck")
(use-package flycheck)
To enable flycheck in a buffer, call M-x flycheck-mode
.
company-mode offers very helpful auto-completion.
company-mode ignores case by default. The configuration below ensures case is preserved.
(message "load company")
(use-package company
:config
(add-hook 'after-init-hook 'global-company-mode)
(setq company-dabbrev-downcase nil)
(setq company-dabbrev-ignore-case nil)
(setq company-keywords-ignore-case nil)
(setq company-dabbrev-code-ignore-case nil)
(setq company-etags-ignore-case nil)
(setq company-idle-delay 0.4))
Use base16-eighties from the base16-theme package.
(message "load base16-theme")
(use-package base16-theme
:config (load-theme 'base16-eighties t))
Undo Tree provides a convenient tool for mapping out previous undo steps. It also restructures undos / redos as a tree, instead of as a linear series of events.
Move undo data to .emacs.d/backups/undo-tree
.
These files were confusing org-roam.
(message "load undo-tree")
(use-package undo-tree
:defer t
:init
(global-undo-tree-mode)
:config
(progn
(evil-set-initial-state 'undo-tree-visualizer-mode 'emacs)
(setq undo-tree-history-directory-alist '(("." . "~/.emacs.d/backups/undo-tree")))))
Add support for multiple cursors.
An overview video of multiple-cursors is available here.
(message "load multiple-cursors")
(use-package multiple-cursors
:init
(global-unset-key (kbd "M-<down-mouse-1>"))
(global-set-key (kbd "M-<mouse-1>") 'mc/add-cursor-on-click))
See pomm for overview of features, commands, and configuration options.
Note that this requires an up-to-date version of transient.el (provided by Magit).
(use-package pomm
:straight t
:commands (pomm pomm-third-time))
An analog indicator of your position in the buffer. With a little help from Nyan Cat.
Use M-x nyan-mode
to enable.
(message "load nyan-mode")
(use-package nyan-mode)
Show the weather with wttr.in
Install wttr.el.
Use etiago’s patch to fix an issue where raw html is shown.
(use-package wttrin
:straight (:type git
:host nil
:repo "git@github.com:etiago/emacs-wttrin.git"
:branch "user-agent-fix")
:custom
(wttrin-default-cities '("Portland")))
(advice-add 'wttrin
:after
(lambda (&rest args)
(setq show-trailing-whitespace nil)
(evil-emacs-state)))
Be sure to follow the installation instructions before using vterm.
Start vterm-mode in Emacs mode; in Normal mode the user is limited to navigating a read-only buffer. Refer to Evil mode for an explanation of Emacs mode versus Vim modes.
Disable highlighting (which is quirky when applied to the terminal).
(unless skip-dev-config
(message "load vterm")
(use-package vterm
:ensure t
:config
(evil-set-initial-state 'vterm-mode 'emacs)
(add-hook 'vterm-mode-hook
(lambda ()
(set (make-local-variable 'global-hl-line-mode) nil)
(setq show-trailing-whitespace nil)))))
(with-eval-after-load 'vterm
(define-key vterm-mode-map (kbd "C-b") 'org-roam-node-find)
(define-key vterm-mode-map (kbd "C-p") 'projectile-command-map)
(define-key vterm-mode-map (kbd "C-.") 'delete-window))
(setq vterm-keymap-exceptions (append vterm-keymap-exceptions '("C-w")))
Git time machine looks like a very useful way of walking through a file’s version history.
Map git-timemachine-toggle
to C-x G
.
Note that C-x g
will still map to jl/magit-status
(a wrapper for magit-status
).
(unless skip-dev-config
(message "load git-timemachine")
(use-package git-timemachine
:config
(evil-set-initial-state 'git-timemachine-mode 'emacs)
(global-set-key (kbd "C-x G") 'git-timemachine-toggle)))
Install markdown-mode.
(unless skip-dev-config
(message "load markdown-mode")
(use-package markdown-mode))
Install json-mode.
(unless skip-dev-config
(message "load json-mode")
(use-package json-mode))
Install json-navigator.
Note: If Emacs complains about a void variable while trying to load the hierarchy package, it is likely due to a dependency pointing to the old version of hierarchy.
In my case, I noticed that in
.emacs.d/straight/repos/melpa/recipes/hierarchy
,
there was the following definition:
(hierarchy :fetcher github :repo "DamienCassou/hierarchy")
.
Deleting this file cleared up the errors I was seeing.
The hierarchy package became a part of Emacs core, so dependency definitions like this should eventually be purged or marked as only applying to older versions of Emacs.
More specifically, it seems like this recipe for hierarchy should either be removed or marked as only applying to older versions of Emacs.
(unless skip-dev-config
(message "load json-navigator")
(use-package json-navigator
:requires hierarchy))
Installs terraform-mode
Includes support for outline-minor-mode.
(unless skip-dev-config
(message "load terraform-mode")
(use-package terraform-mode
:straight t
:custom (terraform-indent-level 2)
:config
(defun my-terraform-mode-init ()
(outline-minor-mode 1))
(add-hook 'terraform-mode-hook 'my-terraform-mode-init)))
Install VLF, a mode for reading very large files in batch.
To view a large file, use M-x vlf
and then enter the file’s path.
(unless skip-dev-config
(message "load vlf")
(use-package vlf))
A minimal setup for working with TypeScript. typescript-mode provides highlight modes for TypeScript.
(unless skip-dev-config
(message "load typescript-mode")
(use-package typescript-mode))
Installs groovy-mode.
(unless skip-dev-config
(message "load groovy-mode")
(use-package groovy-mode))
Install js2-mode
Install js2-mode
and enable for *.js
files.
More tips on how to configure js2-mode and friends is available here.
(use-package js2-mode)
(add-to-list 'auto-mode-alist '("\\.js\\'" . js2-mode))
What better way to code in Lisp than to use Steel Bank Common Lisp and slime? 😁
There are currently two manual steps required before this section will work:
To install sbcl on Debian 12, run: > sudo apt install sbcl sbcl-doc
To install sbcl on Mac, run: > brew install sbcl
To clone the slime repo, run: > git clone git@github.com:slime/slime.org ~/git/slime
(unless skip-dev-config
(message "load slime")
(add-to-list 'load-path "~/git/slime")
(require 'slime-autoloads)
(setq inferior-lisp-program "/usr/bin/sbcl"))
; TODO: apply correct path to sbcl based on platform
;
; On macs, with brew installs of sbcl, path should be:
; (setq inferior-lisp-program "/opt/homebrew/bin/sbcl"))
One of slime’s dependencies, macrostep, has been archived according to it’s readme (on 2024-07-11).
Based on this commit in the emacsorphanage clone of macrostep, it looks like the repo may have a new maintainer.
In the meantime, I cannot find macrostep in MELPA, and calling M-x slime results in the error:
> Symbol’s value as variable is void: macrostep-mode-map
To work around this, I have a copy of macrostep.el taken from here. Based on my testing, loading this file resolves the macrostep error.
(load-file "~/.emacs.d/hacky-contrib/macrostep.el")
With that in place, let’s setup slime.
You will need to install quicklisp and the quicklisp slime-helper for this next part to work.
The basic steps for doing so include:
- Downloading quicklisp.lisp
- Loading this file with: sbcl –load quicklisp.lisp
- Installing quicklisp (in the sbcl session) with: (quicklisp-quickstart:install)
- Loading the quicklisp setup (TODO: is this necessary? if so, why?) (load “~/quicklisp/setup.lisp”)
- Installing quicklisp-slime-helper with: (ql:quickload “quicklisp-slime-helper”)
(slime-setup '(slime-fancy slime-quicklisp slime-asdf slime-mrepl))
; Need to call (ql:quickload "quicklisp-slime-helper")
; then this file will get created for you and you can load it.
(load (expand-file-name "~/quicklisp/slime-helper.el"))
(message "load counsel")
(use-package counsel
:config
(ivy-mode 1)
(setq ivy-use-virtual-buffers t)
(setq ivy-count-format "(%d/%d) "))
Adopting suggested keybindings from here.
(global-set-key (kbd "C-s") 'swiper-isearch)
(global-set-key (kbd "M-x") 'counsel-M-x)
(global-set-key (kbd "C-x C-f") 'counsel-find-file)
(global-set-key (kbd "M-y") 'counsel-yank-pop)
(global-set-key (kbd "<f1> f") 'counsel-describe-function)
(global-set-key (kbd "<f1> v") 'counsel-describe-variable)
(global-set-key (kbd "<f1> l") 'counsel-find-library)
(global-set-key (kbd "<f2> i") 'counsel-info-lookup-symbol)
(global-set-key (kbd "<f2> u") 'counsel-unicode-char)
(global-set-key (kbd "<f2> j") 'counsel-set-variable)
(global-set-key (kbd "C-x b") 'ivy-switch-buffer)
(global-set-key (kbd "C-c v") 'ivy-push-view)
(global-set-key (kbd "C-c V") 'ivy-pop-view)
(global-set-key (kbd "C-c j") 'counsel-git-grep)
(global-set-key (kbd "C-c L") 'counsel-git-log)
(global-set-key (kbd "C-c k") 'counsel-rg)
(global-set-key (kbd "C-c m") 'counsel-linux-app)
;(global-set-key (kbd "C-c f") 'counsel-fzf)
(global-set-key (kbd "C-x l") 'counsel-locate)
(global-set-key (kbd "C-c J") 'counsel-file-jump)
(global-set-key (kbd "C-S-o") 'counsel-rhythmbox)
(global-set-key (kbd "C-c C-r") 'ivy-resume)
(global-set-key (kbd "C-c o") 'counsel-outline)
(global-set-key (kbd "C-c t") 'counsel-load-theme)
(global-set-key (kbd "C-c F") 'counsel-org-file)
Zygospore temporarily hides all but the currently active window.
(message "load zygospore")
(use-package zygospore
:config
(global-set-key (kbd "C-x 1") 'zygospore-toggle-delete-other-windows))
Key Chord Mode lets you execute a command by pressing two keys down at the same time.
The Emacs Wiki has some helpful tips on using this mode.
(message "load key-chord")
(use-package key-chord)
(key-chord-mode 1)
; If this is too long, then there are noticeable typing delays
; If it is too short, then two-key chords are nearly impossible to invoke
; https://github.com/emacsorphanage/key-chord/blob/e724def60fdf6473858f2962ae276cf4413473eb/key-chord.el#L37
(setq key-chord-two-keys-delay 0.025)
(key-chord-define-global "ts" 'save-buffer)
(key-chord-define-global "et" 'evil-avy-goto-char-timer)
(key-chord-define-global "on" 'vterm)
(key-chord-define-global "as" 'zygospore-toggle-delete-other-windows)
vimish-fold lets you fold a region, or lets you fold down to a point specified using avy. It calls out the folded region using the left sidebar (instead of ellipses) which feels a little cleaner.
(message "load vimish-fold")
(use-package vimish-fold
:config
(vimish-fold-global-mode 1)
(global-set-key (kbd "C-c @ a") #'vimish-fold-avy)
(global-set-key (kbd "C-c @ f") #'vimish-fold)
(global-set-key (kbd "C-c @ v") #'vimish-fold-delete)
(global-set-key (kbd "C-c @ U") #'vimish-fold-unfold-all))
docker.el provides support for managing docker containers, images, volumes, networks, contexts and docker-compose.
Because docker.el doesn’t seem to use modes,
used add-hook
with the docker-open-hook
mode hook to switch into Emacs mode
whenever C-c d
is pressed.
(Normal mode masks most, if not all, docker.el bindings).
(message "load docker")
(use-package docker
:ensure t
:bind ("C-c d" . docker)
:config
(add-hook 'docker-open-hook 'evil-emacs-state)
)
Use dockerfile-mode to enable Dockerfile syntax highlighting.
(message "load dockerfile-mode")
(use-package dockerfile-mode)
See kubernetes-el for more information.
Call kubernetes-overview
(or its alias, k8s
)
to get started.
(message "load kubernetes")
(use-package kubernetes
:ensure t
:commands (kubernetes-overview)
:config
(setq kubernetes-poll-frequency 3600
kubernetes-redraw-frequency 3600))
(message "load kubernetes-evil")
(use-package kubernetes-evil
:ensure t
:after kubernetes)
(fset 'k8s 'kubernetes-overview)
(evil-set-initial-state 'kubernetes-mode 'emacs)
(evil-set-initial-state 'kubernetes-logs 'emacs)
(evil-set-initial-state 'kubernetes-log-line 'emacs)
keepass-mode lets you interact with your KeePassXC Database.
(message "load keepass-mode")
(use-package keepass-mode
:config
(evil-set-initial-state 'keepass-mode 'emacs))
Insert timestamp using C-c p
.
(defun insert-current-date ()
"Insert the current date in YYYY-MM-DD format."
(interactive)
(insert (format-time-string "%Y-%m-%d")))
(global-set-key (kbd "C-c p") 'insert-current-date)
(defun jl/first-day-of-year ()
"Get first day of year as list (D M Y)"
(let ((today (calendar-current-date)))
(setq first-day-of-year (copy-sequence today))
(setf (nth 0 first-day-of-year) 1)
(setf (nth 1 first-day-of-year) 1)
first-day-of-year))
(defun jl/days-since-start-of-year ()
"Get number of days since the start of the year"
(let ((today (calendar-current-date))
(first-day-of-year (jl/first-day-of-year)))
(setq today-abs (calendar-absolute-from-gregorian today))
(setq first-day-of-year-abs
(calendar-absolute-from-gregorian first-day-of-year))
(setq days-since-start-of-year
(1+ (- today-abs first-day-of-year-abs)))))
(defun jl/day-of-week-iso (date)
"Given a date in list form (D M Y) returns the day of the week by number, with Monday being 0"
(% (+ (1- (calendar-day-of-week date)) 8) 7))
(defun jl/days-since-first-monday-of-first-week ()
"Get number of days since the start of the year"
(+ (jl/days-since-start-of-year)
(jl/day-of-week-iso (jl/first-day-of-year))))
(defun jl/week-number ()
"Get current week number"
(1+ (/ (jl/days-since-first-monday-of-first-week) 7)))
(defun jl/org-roam-goto-week-by-number ()
"Visit org-roam node with titile 'Week #'"
(interactive)
(org-roam-node-find t (format "Week %s" (jl/week-number))))
Function fresh-start
helps clear any buffers, windows, or frames
that have been opened during the current session. The function does
preserve the *scratch*
buffer, however.
; Code generated with the assistance of ChatGPT, version 3.5, developed by OpenAI
; More information: https://www.openai.com/chatgpt
; Generated on: October 4, 2023
; Jim Ladd updated snippet to use ~delete-other-frames~
; and to move ~delete-other-windows~ outside of ~let~.
(defun fresh-start ()
"Kill all buffers except for *scratch*, close all other windows, and delete all other frames."
(interactive)
;; Close all other frames
(delete-other-frames)
(delete-other-windows)
(let ((buffer-list (buffer-list)))
;; Close all other windows
(dolist (buffer buffer-list)
(unless (string-equal (buffer-name buffer) "*scratch*")
(kill-buffer buffer)))
(message "Fresh start: All buffers except *scratch*, other windows, and frames have been closed.")))
(defun jl/edit-yaml-with-long-values ()
"Edit yaml files with long values"
(interactive)
; hacky test to see if yaml-mode is on
; (couldn't find anything more obvious to key off of)
(unless (equal font-lock-defaults '(yaml-font-lock-keywords))
(yaml-mode))
; truncate long lines
(toggle-truncate-lines 1))
(eval-after-load 'org
'(progn
(unbind-key "C-c C-x f" org-mode-map)
(global-set-key (kbd "C-c C-x f") #'fill-region)))
Sometimes it can be helpful to do the opposite of fill-region.
(defun jl/unfill-paragraph (beg end)
"Unfill the paragraph, joining text into a single logical line"
(interactive "r")
(let ((fill-column (point-max)))
(fill-region beg end)))
(global-set-key (kbd "C-c C-x F") #'jl/unfill-paragraph)
(search-forward-regexp “\n:space:*\n” nil t)
This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line. This is such a long line.
Requesting this behavior is as easy as setting help-window-select
. Perfect.
(setq help-window-select t)
When emacs splits the current window (horizontally or vertically), point remains in the current window. I almost always want to hop over to the new window.
Unfortunately, to make this change we can’t set a global variable or pass in an argument.
Instead, we replace the default function with a lambda
that calls the original function and then calls other-window
.
For more thougts on this tweak – including reasons why advice-add
should not be used –
check out this Stackoverflow question.
(global-set-key "\C-x2" (lambda () (interactive)(split-window-below) (other-window 1)))
(global-set-key "\C-x3" (lambda () (interactive)(split-window-right) (other-window 1)))
Add convenience functions (and keybindings) for adjusting font size of entire frame while preserving frame’s dimensions.
Use C-x C-=
to increase font size.
Use C-x C--
to decrease font size.
Use C-u <number>
before using either of the previous chords
to set how much to increment / decrement font size.
Alternatively, C-x z
(repeat) can be used to repeat the previous command.
Hitting z
after the initial call to C-x z
can be used as a
shortcut for quickly repeating the previous command.
; Resize the whole frame, and not only a window
;; Adapted from:
;; https://stackoverflow.com/questions/24705984/increase-decrease-font-size-in-an-emacs-frame-not-just-buffer
(defun jl/zoom-frame (&optional amt frame)
"Increaze FRAME font size by amount AMT. Defaults to selected
frame if FRAME is nil, and to 1 if AMT is nil."
(interactive "p")
(let* ((frame (or frame (selected-frame)))
(font (face-attribute 'default :font frame))
(size (font-get font :size))
(size (if (eq size 0) 12 size)) ; hack to avoid case where font-get returns size of 0 on macs
(amt (or amt 1))
(new-size (+ size amt)))
(set-frame-font (font-spec :size new-size) t `(,frame))
(message "Frame's font new size: %d" new-size)))
(defun jl/zoom-frame-out (&optional amt frame)
"Call `jl/zoom-frame' with negative argument."
(interactive "p")
(jl/zoom-frame (- (or amt 1)) frame))
(global-set-key (kbd "C-x C-=") 'jl/zoom-frame)
(global-set-key (kbd "C-x C--") 'jl/zoom-frame-out)
The default mode line is long and gets cut off when the frame is split. Update the default mode line to be shorter.
Information on mode line variables can be found here.
(setq-default mode-line-format
'("%e"
evil-mode-line-tag
mode-line-mule-info
mode-line-modified
" "
mode-line-buffer-identification
" "
mode-line-position
mode-line-misc-info
(vc-mode vc-mode)
" "
mode-line-end-spaces))
This is especially helpful when running Emacs on Mac OSX, where Brew apps are otherwise not visible to Emacs.
When running ZSH on Mac, make sure that export PATH=...
statements
are placed in .zprofile
; .zshrc
is not sourced by the function below!
(defun set-exec-path-from-shell-PATH ()
"Set up Emacs' `exec-path' and PATH environment variable to match
that used by the user's shell.
This is particularly useful under Mac OS X and macOS, where GUI
apps are not started from a shell."
(interactive)
(let ((path-from-shell (replace-regexp-in-string
"[ \t\n]*$" "" (shell-command-to-string
"$SHELL --login -c 'echo $PATH'"
))))
(setenv "PATH" path-from-shell)
(setq exec-path (split-string path-from-shell path-separator))))
(set-exec-path-from-shell-PATH)
In practice, not sure that I have ever used auto-save files to recover any data. And in the meantime, they can form cruft that trips up other applications working with the file tree I’m working with.
Note that this does not affect backup files; these are not created in directories managed by a version control system.
See this page for a comparison of backup and auto-save files.
(setq auto-save-default nil)
When looking at directories outside of Emacs (e.g. using a regular shell),
directory contents can quickly become congested by Emacs backup files.
Move them to /.emacs.d/backups/emacs
instead.
(setq backup-directory-alist `(("." . "~/.emacs.d/backups/emacs")))
After Emacs starts, start the Emacs server
so that we can quickly open new sessions with emacsclient
.
(defun sole-emacs-process ()
"Determine if this is the only emacs process that is running."
(let ((output-buffer (generate-new-buffer "*emacs-process-count*")))
; TODO: Mac version cannot use any "--" flags
; Instead it should use something like pgrep Emacs
; and then count the number of lines returned
; Should probably take a similar approach for linux
; if it's possible to take the same approach on both OSes
;(call-process "pgrep" nil output-buffer nil "--exact" "--count" "emacs")
(call-process "pgrep" nil output-buffer nil "--exact" "--count" "emacs")
(save-excursion
(with-current-buffer "*emacs-process-count*"
(end-of-buffer)
(join-line)
(let ((buffer-contents (buffer-substring-no-properties (point-min) (point-max))))
(kill-buffer output-buffer)
(string= buffer-contents "1"))))))
(if (sole-emacs-process)
(server-start))
Previous attempts to start an emacs server from the commandline
using emacs --daemon
have been unsuccessful up to this point;
the command loads init files very differently for some reason.
It begins with the site-wide init files (under /etc
),
and when it finishes with that and tries loading init files in
HOME/emacs.d
it does not start with init.el
.
Create a script, ec
, that will call emacsclient
and note that the command should create a new frame.
(let* ((bin_dir "~/bin")
(ec_script (concat bin_dir "/ec")))
(make-directory bin_dir t)
(if (not (file-exists-p ec_script))
(progn
(find-file "~/bin/ec")
(insert "#!/bin/bash\nemacsclient -c")
(save-buffer)
(kill-buffer)
(set-file-modes ec_script #o755))))
;(profiler-stop)
;(profiler-report)