This document tangles (in literate programming style) the necessary commands to initialize Emacs to my liking and the documentation for my choices.
To clone, go to the github repository. For a pretty view, head over to the generated page.
My init.el
file is quite simple and is generated by the following
block. Essentially, I just have to install this package
(bnb-emacs
) in the ~/.emacs.d/ directory and run the following
code block (C-c C-c
) to bootstrap the system.
;;; init.el --- bnbeckwith config -*- eval: (read-only-mode 1) -*-
;; START ELPACA INSTALLER
(defvar elpaca-installer-version 0.7)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
:ref nil :depth 1
:files (:defaults "elpaca-test.el" (:exclude "extensions"))
:build (:not elpaca--activate-package)))
(let* ((repo (expand-file-name "elpaca/" elpaca-repos-directory))
(build (expand-file-name "elpaca/" elpaca-builds-directory))
(order (cdr elpaca-order))
(default-directory repo))
(add-to-list 'load-path (if (file-exists-p build) build repo))
(unless (file-exists-p repo)
(make-directory repo t)
(when (< emacs-major-version 28) (require 'subr-x))
(condition-case-unless-debug err
(if-let ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
((zerop (apply #'call-process `("git" nil ,buffer t "clone"
,@(when-let ((depth (plist-get order :depth)))
(list (format "--depth=%d" depth) "--no-single-branch"))
,(plist-get order :repo) ,repo))))
((zerop (call-process "git" nil buffer t "checkout"
(or (plist-get order :ref) "--"))))
(emacs (concat invocation-directory invocation-name))
((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
"--eval" "(byte-recompile-directory \".\" 0 'force)")))
((require 'elpaca))
((elpaca-generate-autoloads "elpaca" repo)))
(progn (message "%s" (buffer-string)) (kill-buffer buffer))
(error "%s" (with-current-buffer buffer (buffer-string))))
((error) (warn "%s" err) (delete-directory repo 'recursive))))
(unless (require 'elpaca-autoloads nil t)
(require 'elpaca)
(elpaca-generate-autoloads "elpaca" repo)
(load "./elpaca-autoloads")))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))
;; END ELPACA INSTALLER
;; Install use-package support
(elpaca elpaca-use-package
;; Enable :elpaca use-package keyword.
(elpaca-use-package-mode)
;; Assume :elpaca t unless otherwise specified.
(setq elpaca-use-package-by-default t))
(elpaca-wait)
(load "~/.emacs.d/full.el")
From there on, the bootstrapping is simple. Emacs finds ~/.emacs.d/init.el and runs the code.
First, I disable package
, and use the standard elpaca
initialization. Following that, I enable elpaca-use-package
so that
the rest of this setup can use the standard use-package interface.
Finally, the output of this file, ~/.emacs.d/full.el, is loaded for the complete initialization.
If you are reading this online, the html version of this file is generated by using `bnb/export-readme` explained in Styled HTML Export.
This section has specific notes that are relevant to my emacs setup in general and this document in particular.
A newer package manager, Elpaca seeks to be a preferred drop-in
replacement for the built-in package.el
manager. For the
configuration above, I use the standard installation boilerplate
and enable use-package support.
This is the quick cheat sheet of commands:
Operation | UI (keys apply in elpaca-ui-mode) | completing-read interface commands |
---|---|---|
Finding Packages | M-x elpaca-manager | elpaca-try |
Trying Packages (for current session) | i x | elpaca-try |
Fetching Package Updates | f x | elpaca-fetch or elpaca-fetch-all |
Merging Updates | u x | elpaca-merge or elpaca-merge-all |
Pulling Updates* | C-u u x | C-u M-x elpaca-merge or C-u M-x elpaca-merge-all |
Rebuilding Packages | r x | elpaca-rebuild |
Deleting Packages | d x | elpaca-delete |
View Package Logs | l filters log to current package | elpaca-log |
Visit Package Repository Directory | v | elpaca-visit |
Visit Package Build Directory | C-u v | C-u M-x elpaca-visit |
Browse Package Website | b | elpaca-browse |
There is also a helpful manual.
To see how the startup time has imporoved, let’s store when we start evaluating these settings.
(setq bnb/start-time (float-time))
To aid with general code that needs to run after elpaca
, the
following macro sets up the right hook.
(defmacro with-after-elpaca-init (&rest body)
"Adds BODY to `elpaca-after-init-hook`"
`(add-hook 'elpaca-after-init-hook (lambda () ,@body)))
My current flavor of Emacs comes from: https://github.com/d12frosted/homebrew-emacs-plus
To install with brew
, run the following command:
brew install emacs-plus@29 --with-nobu417-big-sur-icon --with-imagemagick --with-native-comp
And then, to link in /Applications
, run the following command:
oascript -e 'tell application "Finder" to make alias file to posix file "<prefix>/Emacs.app" at POSIX file "/Applications"
Notes: What’s new in 29.1
There are some features that I like to take on a trial run. These are marked with the PENDING tag to help me remember and evaluate.
By utilizing elements of use-package
, I can keep an eye on
troublesome packages during startup. Together, these turn on
reporting and set the minimum time to consider when building the
report.
(setq use-package-verbose t
use-package-compute-statistics t
use-package-minimum-reported-time 0)
The generated messages will be found in the *Messsages*
buffer.
There is also the elpaca log that can show loading times.
This sections houses the settings that need to be made up front and support subsuquent package installation and activiation.
The full name is used for email messages. And bnb/biblio
is set to a sensible default.
These can be overridden in <username>.var.el
.
(setq user-full-name "Benjamin Beckwith"
bnb/biblio '("~/references.bib"))
I typically use the customize interface to generate any local settings such as proxies, paths, fonts, etc. that may vary from machine to machine. This keeps the setup the same and allows for only some details to differ.
I like to set the custom file explicitly. Mine resides in the ~/.emacs.d/ directory. This code block sets the file name and loads it if it exists.
(setq custom-file "~/.emacs.d/custom.el")
(if (file-exists-p custom-file)
(load-file custom-file))
If the file doesn’t exist, Emacs
will still use the file if any
changes are made through the custom interface.
Sometimes I’ll get bad settings or cruft in that file. I now have a
key, <F7>
, for easy navigation to wherever the custom-file
points.
I also intend to have a generic call to an installed local file
that may need to behave differently from custom.el
. This loads
last so that it can modify any existing setting made here to work
on the specific system in question.
In the code below, I add ~/.emacs.d/
to the load path and have a
protected call to load-library. If the file exists, it gets
loaded, otherwise the error normally returned if the file is
non-existent gets ignored.
(condition-case err
(progn
(load-file (format "~/.emacs.d/%s.vars.el" user-login-name))
(message "Loaded local settings file %s.vars.el" user-login-name))
(file-error
(message "Skipping %s.vars library as it does not exist." user-login-name))
nil)
There are a few optimizations to make so that emacs can get
initialized quickly. First, setup the garbage collector to have a
default value of 16mb and a initializtion time value of
most-positive-fixnum
.
This prevents garbage collection from pausing evaluation during startup. After startup, I leverage the hook to reset the optimizations down to their default values.
The next setting stores file-name-handler-alist and then sets it to
nil
. By doing this, there is no automatic handler evalutation
happening during startup. This setting goes back to its original
value post startup.
Finally, user interface elements are hidden early to have a nice streamlined interface.
;; Disable package enabling at startup
(setq package-enable-at-startup nil)
;; Tweak garbage collection threshold
(defvar default-gc-cons-threshold 16777216 ; 16mb
"my default desired value of `gc-cons-threshold'
during normal emacs operations.")
;; make garbage collector less invasive
(setq
gc-cons-threshold most-positive-fixnum
gc-cons-percentage 0.6)
(setq
default-file-name-handler-alist file-name-handler-alist
file-name-handler-alist nil)
(add-hook
'emacs-startup-hook
(lambda (&rest _)
(setq
gc-cons-threshold
default-gc-cons-threshold
gc-cons-percentage 0.1
file-name-handler-alist default-file-name-handler-alist)
;; delete no longer necessary startup variable
(makunbound 'default-file-name-handler-alist)))
The block above is written to ~/.emacs.d/early-init.el and automatically evaluated first by emacs.
(defmacro comment (&rest body)
"Comment out sexps in BODY"
nil)
This section hosts early loading of libraries required by subsequent packages.
The modern list library, Dash, provides a set of common list manipulation functions (all prepended with ‘-‘, hence the name).
(use-package dash
:ensure t)
The mode line can get pretty busy showing all of the package names. Delight helps tone it down by removing some packages from showing up, or changing their name to something shorter.
In use-package
delcarations, I use the :delight
keyword to set a
string to represent the package. It is also possible to provide
elisp for evaluation.
(use-package delight :ensure t)
Sometimes it is useful to go into a command mode that lets you quickly do a few different actions. Hydra does that and more.
By defining specific hydras, you can group together commands with documentation. Think of it as a mini-control-panel. I include it here and use it elsewhere when grouping commands. (See Toggle Map for an example)
(use-package hydra
:ensure t)
(use-package major-mode-hydra
:ensure t
:demand t
:bind ("s-." . major-mode-hydra))
Magit needs an updated seq, so we can install it here. Note that the functions below unload the library if already loaded, and then does the correct install.
;;; Take care of the seq dependency
(defun +elpaca-unload-seq (e)
(and (featurep 'seq) (unload-feature 'seq t))
(elpaca--continue-build e))
(defun +elpaca-seq-build-steps ()
(append (butlast (if (file-exists-p (expand-file-name "seq" elpaca-builds-directory))
elpaca--pre-built-steps elpaca-build-steps))
(list '+elpaca-unload-seq 'elpaca--activate-package)))
(use-package seq :ensure `(seq :build ,(+elpaca-seq-build-steps)))
;;; Wait for this to be processed before packages that depend on it
(elpaca-wait)
The sections here contain mostly settings that configure keymaps, command launchers, built-in features, and other details for day-to-day life.
These sections contain setting related to keys and keymaps.
For binding keys, I use the bind-key package. Not only does it easily bind keys, but it does so with some nice features.
(with-after-elpaca-init
(bind-keys ("C-h B" . describe-personal-keybindings)
("<f7>" . (lambda () (interactive (find-file custom-file))))))
By using bind-key
, you can specify the keystrokes that invoke a
command. In the example above, we bind functions to the global key
map. Note that in later settings, there are also examples of
mapping keys within local keymaps.
If you also want to override any possible minor-mode bindings of
the same keys, you can use bind-key*
instead.
There is also an unbind-key
to, of course, remove any binding.
The real kicker is that it will keep track of these bindings and let you see a summary of your customizations with
M-x describe-personal-keybindings
This is bound to C-h B
above.
As of emacs 28.1, there is a setting to group bindings into an
outline format. Use the following settings with M-x
describe-bindings
or C-h b
.
(setq describe-bindings-outline t)
The following settings are inspired from http://endlessparentheses.com/the-toggle-map-and-wizardry.html.
This toggle map shows the current toggleable settings with
shortcut keys for enabling. The amaranth color makes this buffer
stay around until I press q
.
What are these settings?
Key | Function | Description |
---|---|---|
c | column-number-mode | Toggle column number display in the modeline |
e | toggle-debug-on-error | Enter debugger on error |
u | toggle-debug-on-quit | Enter debugger on C-g |
f | auto-fill-mode | Automatic line breaking |
t | toggle-truncate-lines | Truncate long lines in the buffer |
r | dired-toggle-read-only | Read-only mode |
w | whitespace-mode | Whitespace visualization |
b | orgtbl-mode | Use org table minor mode (non-org buffers) |
x | bnb/transparency-next | Cycle forward through transparency settings |
X | bnb/transparency-previous | Cycle backward through transparency settings |
B | display-battery-mode | Show battery info in modeline |
l | hl-line-mode | Highlight current line |
m | bnb/hide-mode-line-mode | Toggle mode line |
(with-after-elpaca-init
(pretty-hydra-define hydra-toggle (:color amaranth :quit-key "q" :title " TOGGLES")
("Basic"
(("c" column-number-mode "col number" :toggle t)
("l" hl-line-mode "highlight line" :toggle t)
("f" auto-fill-mode "auto-fill" :toggle t)
("t" toggle-truncate-lines "truncate lines" :toggle truncate-lines))
"Minor"
(("r" rainbow-mode "rainbow" :toggle t)
("w" whitespace-mode "whitespace" :toggle t)
("b" orgtbl-mode "Org table" :toggle t)
("R" dired-toggle-read-only "dired read only" :toggle t))
"UI"
(("m" bnb/hide-mode-line-mode "hide mode line" :toggle t)
("B" display-battery-mode "display battery" :toggle t)
("x" bnb/transparency-next "transparency next")
("X" bnb/transparency-previous "transparency prev"))
"Emacs"
(("D" toggle-debug-on-error "debug on error" :toggle (default-value 'debug-on-error))
("X" toggle-debug-on-quit "debug on quit" :toggle (default-value 'debug-on-quit)))))
(bind-key "C-x t" 'hydra-toggle/body))
This mode (used in the keymap above) toggles a mode that shows the different whitespace in a buffer.
(use-package whitespace
:ensure nil
:commands (whitespace-mode)
:custom
(whitespace-line-column nil)
:delight " 🟂")
By default, M-\
performs delete-horizontal-space
and will
consume all of the whitespace present.
I’d like it to be smart and leave one or no spaces if
possible. The fixup-whitespace
function will do that.
(with-after-elpaca-init
(bind-key "M-k" 'fixup-whitespace))
In addition to moving the cursor, it is also interesting to scroll the screen (without moving the cursor with respect to the frame).
(defun bnb/scroll-up-1 ()
"Scroll up by one line."
(interactive)
(cua-scroll-up 1))
(defun bnb/scroll-down-1 ()
"Scroll down by one line."
(interactive)
(cua-scroll-down 1))
(with-after-elpaca-init
(with-eval-after-load 'bind-key
(bind-keys
("M-n" . bnb/scroll-up-1)
("M-p" . bnb/scroll-down-1))))
When selecting a region, a quick trip to align-regexp can align all of that nasty text.
(with-after-elpaca-init
(with-eval-after-load 'bind-key
(bind-key "C-c TAB" 'align-regexp)))
Another great tip from Pragmatic Emacs, use kill-this-buffer to
kill the current buffer instead of asking which one. I’m not
overriding the C-x k
default, but added a C-x C-k
alternative.
(defun bnb/kill-this-buffer ()
"Kill the current buffer"
(interactive)
(kill-buffer (current-buffer)))
(with-after-elpaca-init
(bind-keys
("C-x C-k" . bnb/kill-this-buffer)))
I like to be able to use the command (or super or hyper) keys for shortcuts. I need to take care to not interfere with the built-in operating system shortcuts or my bindings will not work.
(setq mac-function-modifier 'hyper
mac-pass-command-to-system nil
mac-right-option-modifier 'none
mac-right-command-modifier 'hyper
mac-right-control-modifier 'hyper
mac-command-modifier 'meta
mac-control-modifier 'ctrl
mac-option-modifier 'super)
Note that the right option
and command
keys will pass through to
the system. This is especially cool for the option
key on a mac
that lets insert special characters directly. (E.g. á or ∑ or ®)
Inspiration for the keys comes from wisdom and wonder.
This section holds the settings for my two main command launchers: hydra and vertico.
Sometimes it is useful to go into a command mode that lets you quickly do a few different actions. Hydra does that and more.
By defining specific hydras, you can group together commands with documentation. Think of it as a mini-control-panel. I include it here and use it elsewhere when grouping commands. (See Toggle Map for an example)
The setup is in Hydra so that I can use it with the previous keybinding commands.
Or VERTical Interactive COmpletion, is my preferred completion interface.
(use-package vertico
:ensure t
:config (vertico-mode))
The directory extension navigates directories like Ido
.
;; Configure directory extension.
(use-package vertico-directory
:after vertico
:ensure nil
;; More convenient directory navigation commands
:bind (:map vertico-map
("RET" . vertico-directory-enter)
("DEL" . vertico-directory-delete-char)
("M-DEL" . vertico-directory-delete-word)
("?" . minibuffer-completion-help))
;; Tidy shadowed file names
:hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
The embark package helps find actions relevant to what is near
the point. With C-.
, a menu pops up with actions to choose from.
(use-package embark
:ensure t
:bind
(("C-." . embark-act)
("C-;" . embark-dwim)
("C-x ." . embark-act)
("C-x ;" . embark-dwim)
("C-h C-b" . embark-bindings))
:init
(setq prefix-help-command #'embark-prefix-help-command)
:config
(add-to-list 'display-buffer-alist
'("\\'\\*Embark Collect \\(Live\\|Comletions\\)\\*"
nil
(window-parameters (mode-line-format . none)))))
(use-package embark-consult
:after (emark consult)
:ensure t
:hook
(embark-collect-mode . consult-preview-at-point-mode))
Easy completion is possible with Orderless. This completion framework lets users utilize matching elements separated by spaces.
(use-package orderless
:ensure t
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles basic partial-completion)))))
Within the matching framework, a few dispatchers can modify the subsequent matchers. The following table summarizes these elements.
Character | Effect |
---|---|
! | Does not match following literal |
, | Matches initial characters |
=== | Forces a literal match |
~ | Uses the flex matching |
% | Matches while ignoring diacritics |
Rounding out the completion helpers, Consult provides specific functions that help complete actions or find elements. The bindings are supplied below.
(use-package consult
:ensure t
:bind (;; C-c bindings
("C-c h" . consult-history)
("C-c m" . consult-mode-command)
("C-c b" . consult-bookmark)
("C-c k" . consult-macro)
("C-c o" . consult-outline)
;; C-x bindings
("C-x b" . consult-buffer)
("C-x 4 b" . consult-buffer-other-window)
("C-x 5 b" . consult-buffer-other-frame)
("C-x r x" . consult-register)
("C-x r b" . consult-bookmark)
;; Custom M bindings
("M-g o" . consult-ouline)
("M-y" . consult-yank-pop)
("M-i" . consult-imenu))
:config
(defvar bnb/org-agendas
(list :name "Org Agenda Files"
:category 'file
:narrow ?a
:face 'consult-file
:history 'file-name-history
:action #'consult--file-action
:items #'org-agenda-files))
(add-to-list 'consult-buffer-sources 'bnb/org-agendas 'append)
:init
(fset 'multi-occur #'consult-multi-occur))
One of the more interesting feaures is virtual buffers. When viewing buffers, recent files, bookmarks, and similar, the interface shows the buffer as you are selecting so that you can have the right file context for the line you are selecting.
The consult-buffer command is powerful and has specific key sequences that can narrow the buffer list in useful ways. These are summarized in the following list.
b <SPC>
- buffers
<SPC>
- hidden buffers
* <SPC>
- modified buffers
f <SPC>
- files
r <SPC>
- file registers
m <SPC>
- bookmarks
p <SPC>
- project
In the code block above, I add one more, a <SPC>
that will show
the available org-agenda-files for easy selection.
The great thing about vertical completion is the extra horizontal space. Marginalia makes use of this extra space by providing relevant extra information about each element on the line.
(use-package marginalia
:ensure t
:bind (:map minibuffer-local-map
("M-A" . marginalia-cycle))
:init
(marginalia-mode)
:config
(setq marginalia-annotators
'(marginalia-annotators-heavy marginalia-annotators-light)))
This section defines interations with text expansion systems.
The following block is courtesy of Endless Parentheses. For regular misspellings, we can do ispell and then make an abbreviation for future corrections.
(defun bnb/ispell-word-then-abbrev (p)
"Call `ispell-word'. Then create an abbrev for the correction
made. With prefix P, create local abbrev. Otherwise, it will be
global."
(interactive "P")
(let ((bef (downcase (or (thing-at-point 'word) ""))) aft)
(call-interactively 'ispell-word)
(setq aft (downcase (or (thing-at-point 'word) "")))
(unless (string= aft bef)
(message "\"%s\" now expands to \"%s\" %sally"
bef aft (if p "loc" "glob"))
(define-abbrev
(if p global-abbrev-table local-abbrev-table)
bef aft))))
(use-package abbrev
:ensure nil
:delight " ⚆"
:bind (("C-x C-i" . bnb/ispell-word-then-abbrev))
:config
(setq save-abbrevs t)
(setq-default abbrev-mode t))
“Let your completions fly!” – cape.el
Cape provies a set of completion backends avaialble right on bound keys. It works with Corfu.
;; Add extensions
(use-package cape
:ensure t
;; Bind dedicated completion commands
;; Alternative prefix keys: C-c p, M-p, M-+, ...
:bind (("C-c p p" . completion-at-point) ;; capf
("C-c p t" . complete-tag) ;; etags
("C-c p d" . cape-dabbrev) ;; or dabbrev-completion
("C-c p h" . cape-history)
("C-c p f" . cape-file)
("C-c p k" . cape-keyword)
("C-c p s" . cape-elisp-symbol)
("C-c p e" . cape-elisp-block)
("C-c p a" . cape-abbrev)
("C-c p l" . cape-line)
("C-c p w" . cape-dict)
("C-c p :" . cape-emoji)
("C-c p \\" . cape-tex)
("C-c p _" . cape-tex)
("C-c p ^" . cape-tex)
("C-c p &" . cape-sgml)
("C-c p r" . cape-rfc1345))
:init
;; Add to the global default value of `completion-at-point-functions' which is
;; used by `completion-at-point'. The order of the functions matters, the
;; first function returning a result wins. Note that the list of buffer-local
;; completion functions takes precedence over the global list.
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-elisp-block)
;;(add-to-list 'completion-at-point-functions #'cape-history)
;;(add-to-list 'completion-at-point-functions #'cape-keyword)
;;(add-to-list 'completion-at-point-functions #'cape-tex)
;;(add-to-list 'completion-at-point-functions #'cape-sgml)
;;(add-to-list 'completion-at-point-functions #'cape-rfc1345)
;;(add-to-list 'completion-at-point-functions #'cape-abbrev)
;;(add-to-list 'completion-at-point-functions #'cape-dict)
;;(add-to-list 'completion-at-point-functions #'cape-elisp-symbol)
;;(add-to-list 'completion-at-point-functions #'cape-line)
)
Taking in-buffer completion to the next level, Corfu gives familar functionality with nice enhancements. It integrates with orderless for easier searching, and has the ability to show documentation alongside of the completion popup.
(use-package corfu
:ensure t
:custom
(corfu-auto nil)
(tab-always-indent 'complete)
:bind
(:map corfu-map ("SPC" . corfu-insert-separator))
:init
(global-corfu-mode)
(corfu-popupinfo-mode 1))
Because Corfu uses child frames, terminal support needs to be added that leverages overlays for non-graphical frames.
(use-package corfu-terminal
:if (not (display-graphic-p))
:ensure (corfu-terminal
:host github
:repo "https://codeberg.org/akib/emacs-corfu-terminal.git"))
Text expansion makes sense in many programming modes. Yasnippet comes in handy by providing a minor mode for easy expansions.
(use-package yasnippet
:ensure t
:defer 30
:hook
(prog-mode . yas-minor-mode)
(text-mode . yas-minor-mode)
:config
(yas-reload-all))
I also load a collection of yasnippet snippets so I don’t have to maintain my own.
(use-package yasnippet-snippets
:ensure t)
Try to expand the text before point in an intelligent way. Repeat the keypress to cycle through options.
(with-after-elpaca-init
(bind-key "M-/" 'hippie-expand))
Emacs comes with some nice batteries. This section configures my favorites.
Sensible backup settings from https://www.emacswiki.org/emacs/BackupDirectory
(setq backup-by-copying t
create-lockfiles nil
backup-directory-alist '((".*" . "~/.emacs.d/.saves"))
;; auto-save-file-name-transforms `((".*" "~/.saves" t))
kill-buffer-delete-auto-save-files t
delete-old-versions t
kept-new-versions 6
kept-old-versions 2
version-control t)
Here’s a quick rundown of the settings:
- backup-by-copying
- Use copying to create backups when
t
- create-lockfiles
- Don’t use lockfiles if
nil
- backup-directory-alist
- List of regexp/location pairs of where to backup files
- kill-buffer-delete-auto-save-files
- Killing a buffer with an auto-save file will prompt for deletion
- delete-old-versions
- Delete excess backups silently if
t
- kept-new-versions
- Number of newest versions to keep
- kept-old-versions
- Number of oldest versions to keep
- version-control
- When
t
, make numeric backup versions always
I like to have the files be utf-8
by default. Do
let me know if I shouldn’t do this, will you?
Set utf-8
for all coding systems except for the clipboard on
windows. That one gets utf-16le
to be compatible.
(prefer-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-language-environment 'utf-8)
(setq buffer-file-coding-system 'utf-8
x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))
;; MS Windows clipboard is UTF-16LE
(when (eq system-type 'windows-nt)
(set-clipboard-coding-system 'utf-16le-dos))
Emacs 28.1 introduced Native Compilation. When this feature is available, I use it to compile the packages.
There are also two settings to make the process slightly more verbose and ensure that warnings and erros are bubbled up from any async processes.
(if (native-comp-available-p)
(setq package-native-compile t
native-comp-verbose 1
native-comp-async-report-warnings-errors t))
Sometimes Emacs’ idea of path differs from the shell. The package exec-path-from-shell seeks to bring those in line with each other.
(use-package exec-path-from-shell
:ensure t
:defer t
:config
(when (memq window-system '(mac ns x))
(exec-path-from-shell-initialize)))
Using Emacs as a server is a great way to keep the power responsive.
(when (and (or (eq system-type 'windows-nt) (eq system-type 'darwin))
(not (and (boundp 'server-clients) server-clients))
(not (daemonp)))
(server-start))
I dislike the bell ringing when I hit C-g
. To silence the bell,
just set the ring-bell-function
to nil
.
(setq visual-bell nil
ring-bell-function `(lambda () nil))
I enable emacs remembering recently open files. For my setup, this feeds into the candidates for Consult.
(with-after-elpaca-init
(recentf-mode t))
For world-clock, it’s best to define the time zones most relevant to me. For compatible time zones, check this handy list.
(setq zoneinfo-style-world-list
'(("America/New_York" "CBUS")
("America/Los_Angeles" "San Fran")
("Europe/London" "London")
("Australia/Sydney" "Sydney")
("Asia/Kolkata" "Bangalore")))
Folding quotes will allow isearch to find similar characters to the ones being searched for.
;; New in Emacs 29
(setq isearch-fold-quotes-mode t)
This section holds any minibuffer
settings.
Let’s get rid of duplicates in the minibuffer history.
(setq history-delete-duplicates t)
This saves the minibuffer histories to preserve across emacs sessions.
(with-after-elpaca-init
(setq savehist-additional-variables '(search-ring regexp-search-ring)
savehist-file "~/.emacs.d/savehist")
(savehist-mode t))
Getting around takes a little tweaking. This section holds the details on how movement is defined for me.
The Ace (and subsequent Avy) packages aid in jumping the cursor to the right place in the buffer.
Turn on ace-flyspell when flyspell is enabled. This mode helps jump between the errors (misspellings) discovered by flyspell.
(use-package ace-flyspell
:after (hydra major-mode-hydra)
:ensure t
:commands (ace-flyspell-setup)
:bind
("H-s" . hydra-fly/body)
:hook
(flyspell-mode . ace-flyspell-setup)
:init
(pretty-hydra-define hydra-fly (:color pink :quit-key "q" :title " Flyspell")
("Checking"
(("b" flyspell-buffer "Check buffer")
("r" flyspell-region "Check region"))
"Correction"
(("c" ispell-word "Correct word")
("." ace-flyspell-dwim "dwim"))
"Movement"
(("n" flyspell-goto-next-error "Next error")
("j" ace-flyspell-jump-word "Jump word")))))
Supercharge isearch
to vary its behavior depending on the
input. The C-'
key let’s me jump to the isearch match easily
with the ace-jump
methods.
(use-package ace-isearch
:ensure t
:bind (:map isearch-mode-map
("C-'" . ace-isearch-jump-during-isearch))
:delight ace-isearch-mode
:config
(global-ace-isearch-mode t)
(setq ace-isearch-input-length 8))
In modes with links, use o
to jump to links. Map M-o
to do the
same in Orgmode.
(use-package ace-link
:ensure t
:bind (:map org-mode-map ("M-o" . ace-link-org))
:config (ace-link-setup-default))
Instead of C-x o
traversal, ace-window
provides numbers
for quick window access
Set the keys to something other than the default numbers. Note that this also limits the number of windows that can be used, but given my usage, I doubt it goes up to ‘m’ often.
Also, I modify the face attribute to make the window numbers large.
After reading the wiki, I supercharged the interface for ace-window
.
(use-package ace-window
:ensure t
:bind
("H-SPC" . ace-window)
("<f9> a" . ace-window)
:custom
(aw-keys '(?j ?k ?l ?\; ?a ?s ?d ?f))
(aw-leading-char-style 'path)
(aw-dispatch-always t))
(with-after-elpaca-init
(progn
(pretty-hydra-define hydra-window-controls (:color amaranth :quit-key "q" :title " Window controls")
("Window Size"
(("h" shrink-window-horizontally "shrink horizontal")
("j" shrink-window "shrink vertical")
("k" enlarge-window "enlarge vertical")
("l" enlarge-window-horizontally "enlarge horizontal"))
"Scroll other window"
(("n" scroll-other-window "scroll")
("p" scroll-other-window-down "scroll down"))))
(pretty-hydra-define hydra-frame-controls (:color red :title " Frame controls")
("Modification"
(("f" make-frame "new frame")
("x" delete-frame "delete frame"))))
(with-eval-after-load 'ace-window
(progn
(add-to-list 'aw-dispatch-alist '(?w hydra-window-controls/body) t)
(add-to-list 'aw-dispatch-alist '(?F hydra-frame-controls/body) t)
(add-to-list 'aw-dispatch-alist '(?B balance-windows) t)
(set-face-attribute 'aw-leading-char-face nil :height 2.0)))))
Navigating to the right spot in a buffer can be done in an easy fashion with Avy. The collection of goto functions yield a variety of methods to select where to place the point.
In the set of mappings below, it’s easy to see the thing you are targeting (word, char, line), and how you are targeting it. The how is the suffix.
A suffix of 1
means you will input one character to show the
candidates. A suffix of 0
will list all candidates without an
initial selection. A suffix of 2
means you’ll input two
characters before showing candidates. Finally, a suffix of timer
will accept several characters and then show the candidates after
an elapsed timer.
(use-package avy
:ensure t
:bind
("H-." . avy-goto-char-timer)
("H-w" . avy-goto-word-1)
("H-/" . avy-goto-char-2)
("H-l" . avy-goto-line)
("H-d" . avy-goto-word-0)
("<f9> ." . avy-goto-char-timer)
("C-c g" . avy-goto-word-1)
("M-g l" . avy-goto-line)
("M-g ." . avy-goto-char-2)
("M-g w" . avy-goto-word-0))
The commands begin with the normal prefix of M-g
for the goto
commands and use l,c and w for lines, characters and words
respectively.
Zap to char using avy. This is just what is sounds like. You kill everything from point to the selected character.
(use-package avy-zap
:ensure t
:bind
("M-z" . avy-zap-to-char-dwim)
("M-Z" . avy-zap-up-to-char-dwim))
When navigating errors (output from M-x compile
for example), this
highlights the visited error. Although named for errors, this
functionality is also used for M-x occur
and M-x rgrep
and others.
Within the buffer full of errors or matches, M-g M-n/M-p
will
navigate up/down visiting the errors in a separate buffer and
highlighting the current error or match.
(setq next-error-message-highlight t)
For read-only files, look at them in view-mode which will enable vi-style navigation. In this mode, kill commands will save text, but not remove it.
(use-package view
:ensure nil
:delight " 👁"
:init (setq view-read-only t)
:bind (:map view-mode-map
("n" . next-line )
("p" . previous-line)
("j" . next-line )
("k" . previous-line)
("l" . forward-char)
("h" . bnb/view/h)
("q" . bnb/view/q))
:config
(defun bnb/view/h ()
"Setup a function to go backwards a character"
(interactive)
(forward-char -1))
(defun bnb/view/q ()
"Setup a function to quit `view-mode`"
(interactive)
(view-mode -1)))
This is one of those cool finds for a problem I mostly knew that I
had. I often hit C-v
by accident and lose my place. With the
following setting, M-v
completely undoes the scroll leaving the
cursor back in the original position.
(setq scroll-preserve-screen-position 'always)
Thanks to http://irreal.org/blog/?p=3963 for the tip.
This is a collection of code specific to how I use emacs. Some are from different websites or other Emacs users.
John Weigley shows a way to display the agenda after some period of inactivity.
(defun bnb/jump-to-org-agenda ()
"Create and jump to the bnb org agenda."
(interactive)
(let ((buf (get-buffer "*Org Agenda*"))
wind)
(if buf
(if (setq wind (get-buffer-window buf))
(select-window wind)
(if (called-interactively-p)
(progn
(select-window (display-buffer buf t t))
(org-fit-window-to-buffer))
(with-selected-window (display-buffer buf)
(org-fit-window-to-buffer))))
(org-agenda nil "f."))))
(defun bnb/idle-agenda (&optional arg)
"Set or cancel idle agenda timer based on [ARG]."
(interactive "P")
(setq bnb/iagenda
(if arg
(cancel-timer bnb/iagenda)
(run-with-idle-timer 3600 t 'bnb/jump-to-org-agenda))))
(with-after-elpaca-init
(bnb/idle-agenda))
Automatically indent pasted blocks of text.
(dolist (command '(yank yank-pop))
(eval `(defadvice ,command (after indent-region activate)
(and (not current-prefix-arg)
(let ((mark-even-if-inactive transient-mark-mode))
(indent-region (region-beginning) (region-end) nil))))))
http://www.reddit.com/r/emacs/comments/25v0eo/you_emacs_tips_and_tricks/chldury
These settings split the window and load a previous buffer (instead of the same buffer in both). This has a better chance of being what I want when splitting strings.
(defun bnb/vplit-last-buffer ()
"When splitting the frame, load the last visited buffer."
(interactive)
(split-window-vertically)
(other-window 1 nil)
(switch-to-next-buffer))
(defun bnb/hsplit-last-buffer ()
"When splitting the frame, load the last visited buffer."
(interactive)
(split-window-horizontally)
(other-window 1 nil)
(switch-to-next-buffer))
(with-after-elpaca-init
(bind-keys
("C-x 2" . bnb/vplit-last-buffer)
("C-x 3" . bnb/hsplit-last-buffer)))
This is a fun one I picked from a now defunct website. This block of code hides the mode-line for the current buffer (window).
;; Setup buffer-local behavior
(defvar-local bnb/hide-mode-line-mode nil)
;; Setup minor mode
(define-minor-mode bnb/hide-mode-line-mode
"Minor mode to hide mode-line in current buffer"
:init-value nil
:global nil
:variable bnb/hide-mode-line-mode
:group 'editing-basics
(if bnb/hide-mode-line-mode
(setq bnb/hide-mode-line-mode/saved-format mode-line-format
mode-line-format nil)
(setq mode-line-format bnb/hide-mode-line-mode/saved-format
bnb/hide-mode-line-mode/saved-format nil))
(force-mode-line-update)
(redraw-display)
(when (and (called-interactively-p 'interactive)
bnb/hide-mode-line-mode)
(run-with-idle-timer
0 nil 'message
(concat "Goodbye mode line!"
"Use M-x bnb/hide-mode-line-mode to make the mode-line appear"))))
;; Bind global key
(with-after-elpaca-init
(bind-key "H-0" 'bnb/hide-mode-line-mode))
When I hit <F5>
, open this file for editing. That way, any
time I have something I need to remember for my emacs setting, it
is just a button-push away.
(with-after-elpaca-init
(bind-key
"<f5>"
(lambda ()
(interactive)
(find-file "~/.emacs.d/bnb-emacs/Readme.org"))))
In order to resize the face when `org-column` mode is on, some
advice is in order. The face used has a set :height
that is not
overridden by custom face settings.
To have a custom height, this advice prepends the list with an anonymous face with a height of 0.8. This setting happens first, so it wins.
(defun bnb/org-overlay-font-override (orig-fn beg end &optional txt face)
(let ((bnbface (cons '(:height 0.8) face)))
(funcall orig-fn beg end txt bnbface)))
(with-eval-after-load 'org
(advice-add 'org-columns--new-overlay :around #'bnb/org-overlay-font-override))
;(advice-remove 'org-columns--new-overlay #'bnb/org-overlay-font-override)
There is a little bit of boilerplate to get the right set or replacements set for prettify correctly. This is exactly why macros are a thing. This one simplifies the call to provide a mode, and the list of replacements.
(defmacro bnb/prettify (mode replacements)
"Set the prettify REPLACEMETS for MODE in a mode hook"
`(progn
(setq ,(intern (concat "bnb/prettify-" mode "-replacements")) ,replacements)
(defun ,(intern (concat "bnb/prettify-" mode "-setup"))
()
(mapc
(lambda (pair) (push pair prettify-symbols-alist))
,(intern (concat "bnb/prettify-" mode "-replacements")))
(prettify-symbols-mode t))
(add-hook
(quote ,(intern (concat mode "-hook")))
(function ,(intern (concat "bnb/prettify-" mode "-setup"))))))
This is how I get the one-page html output for Github Pages. There are two main parts to setting up and executing the export.
First, I use a SETUPFILE
from
https://github.com/fniessen/org-html-themes. Specifically, I use
the readtheorg style.
Second, I setup the emacs theme correctly for nice code output. Syntax highlighting in the export will pull from the current theme. I don’t want this. Instead, I want to specify which theme to use for every export.
The code below stores away the current list of enabled themes
before disabling them all. Then, it enables my preferred export
theme (sanityinc-tomorrow-day
) before performing the
export. Finally, it disables the last theme and renables all of the
ones on the list.
(defvar bnb/export-theme '(sanityinc-tomorrow-day))
(defun bnb/export-readme ()
"Export the tangled org setting as html.
`bnb/export-theme` sets the theme for the code exports."
(interactive)
(let ((themes custom-enabled-themes)
(file "~/.emacs.d/bnb-emacs/Readme.org"))
(with-temp-buffer
(insert "#+SETUPFILE: https://fniessen.github.io/org-html-themes/org/theme-readtheorg.setup\n")
(insert (format "#+include: %s\n" (file-truename file)))
(org-mode)
(elpaca-wait) ;; ensure all modules are loaded
(mapc 'disable-theme themes)
(mapc 'load-theme bnb/export-theme)
(let ((exported (org-export-as 'html))
(save-silently-p t))
(with-temp-file
(format "%sindex.html" (file-name-directory file))
(insert exported))
(mapc 'disable-theme bnb/export-theme)
(mapcar 'load-theme (reverse themes))))))
The process is to create a temp buffer and insert the setupfile
and an include to this file. Some of the finer points are that I
ensure org-mode
is on, themes are loaded/unloaded correctly and
the export goes to the right file.
Not all of the links I use in this file easily export. Some require some tweaking to show up nicely on the web. This next block sets up some handlers for the link types that need a little extra care and attention.
(defun bnb/export-tooltip (link description format)
"Exporter for help: links"
(let ((desc (or description link)))
(pcase format
('html (format "<span class=\"tooltip\"><code>%s</code>%s</span>" desc (bnb/make-doc-tooltip desc)))
(_ desc))))
(defun bnb/space-to-html-entity (text)
"Change spaces to html entities in TEXT."
(string-replace " " " " text))
(defun bnb/linebreak-to-html-entity (text)
"Change linebreaks to html entities in TEXT."
(string-replace "\n" "<br>" text))
(defun bnb/html-entity-replacement (text)
"Perform html entity conversions on TEXT."
(bnb/linebreak-to-html-entity
(bnb/space-to-html-entity text)))
(defun bnb/make-doc-tooltip (element)
"Pop out tooltip text if we have it"
(condition-case err
(let* ((template "<span class=\"tooltiptext\">%s</span>")
(sym (intern element))
(doc (if (symbolp sym)
(or (documentation-property sym 'variable-documentation)
(documentation sym))
"")))
(format template (bnb/html-entity-replacement doc)))
(error (message "Skipping Error: %s" err))))
(defun bnb/export-help-links (link description format)
(bnb/export-tooltip link description format))
(defun bnb/export-org-ql-links (link description format)
(let ((desc (or description link)))
(pcase format
('html (format "<span class=\"tooltip\"><code>%s</code>%s</span>" desc "<span class=\"tooltiptext\">Org QL search links only work in Emacs.</span>"))
(_ desc))))
(with-eval-after-load 'org
(org-link-set-parameters "help" :export #'bnb/export-help-links)
(org-link-set-parameters "org-ql-search" :export #'bnb/export-org-ql-links))
Using the ring
package, these commands will cycles through
transparency settings.
The transparency ring variable holds cells that determing the focused and unfocused opacity settings in terms of percentage.
(use-package ring
:ensure nil
:commands (bnb/transparency-apply bnb/transparency-next bnb/transparency-previous
bnb/transparency-cycle bnb/transparency-add)
:config
(setq bnb/transparency-ring
(ring-convert-sequence-to-ring (list '(100 100) '(100 50) '(100 10) '(95 50) '(90 50) '(85 50)))
bnb/transparency
(ring-ref bnb/transparency-ring 0))
(defun bnb/transparency-apply (trans)
"Apply the TRANS alpha value to the frame."
(set-frame-parameter (selected-frame) 'alpha (setq bnb/transparency trans)))
(defun bnb/transparency-next ()
"Apply the next transparency value in the ring `bnb/transparency-ring`."
(interactive)
(bnb/transparency-apply (ring-next bnb/transparency-ring bnb/transparency)))
(defun bnb/transparency-previous ()
"Apply the previous transparency value in the ring `bnb/transparency-ring`."
(interactive)
(bnb/transparency-apply (ring-previous bnb/transparency-ring bnb/transparency)))
(defun bnb/transparency-cycle ()
"Cycle to the next transparency setting."
(interactive)
(bnb/transparency-next))
(defun bnb/transparency-add (active inactive)
"Add ACTIVE and INACTIVE transparency values to the ring."
(interactive "nActive Transparency:\nnInactive Transparency:")
(ring-insert+extend bnb/transparency-ring (list active inactive) t)
(bnb/transparency-apply (list active inactive))))
I use a scoring system to keep track of my overall progress. This involves scoring my tasks and attributing my idea of impact of a particular done item.
To use these numbers, I do a weekly review on Monday and compare the numbers to past years/weeks/etc. To keep pushing forward, this little bit of code will insert a running status at the top of my agenda.
If I am on track for the given day (at or above the scaled goal), all is green. Otherwise, I get a warning type formatting above 80% and error type formatting under.
;; Define my goal to hit
(defvar bnb/weekly-score-goal 42)
;; Add up all the scores from DONE items in the agenda files
(defun bnb/agenda-score-goal ()
"Add up scores from done items.
In the agenda, this will show the number of done items and the
target goal from `bnb/weekly-score-goal`."
(let* ((score ;; Add up all scores from DONE items
(apply '+
(org-map-entries
(lambda () (string-to-number (or (org-entry-get (point) "Score") "0")))
"/DONE" 'agenda)))
(scaled-goal (* bnb/weekly-score-goal
(/ (string-to-number (format-time-string "%w"))
5.0)))
(face (cond ((>= score scaled-goal) 'success)
((>= score (* .8 scaled-goal)) 'warning)
(t 'error)))
(goal-label (format "✧ Score Goal (%d): " scaled-goal))
(goal-metric (format "%d/%d\n" score bnb/weekly-score-goal))
(header-size (+ (string-width goal-label)
(string-width goal-metric)))
(goal-separator (concat (make-string header-size ?┄) "\n")))
(insert
(concat
(propertize goal-label 'face 'org-agenda-structure)
(propertize goal-metric 'face face)
(propertize goal-separator 'face 'org-agenda-structure)))))
;; This hook runs first in the agenda (and before it is set to read-only)
(with-eval-after-load 'org
(add-hook 'org-agenda-mode-hook 'bnb/agenda-score-goal))
This is a function to create an entry like a datetree, but using years and workweeks instead.
(defun bnb/find-year-create (year)
"Find or create a [YEAR] in an Org journal."
(let ((re "^\\**[ \t]+\\([12][0-9]\\{3\\}\\)")
match)
(org-narrow-to-subtree)
(goto-char (point-min))
(while (and (setq match (re-search-forward re nil t))
(goto-char (match-beginning 1))
(< (string-to-number (match-string 1)) year)))
(cond
((not match)
(goto-char (point-max))
(or (bolp) (newline))
(insert (format "** %s\n" year)))
((= (string-to-number (match-string 1)) year)
(goto-char (point-at-bol)))
(t
(beginning-of-line 1)
(insert (format "** %s\n" year))))))
(defun bnb/find-ww-create (ww)
"Find or create a [WW] (workweek) in an Org journal."
(let ((re "^\\**[ \t]+\\WW\\([0-9]\\{2\\}\\)")
match)
(org-narrow-to-subtree)
(goto-char (point-min))
(while (and (setq match (re-search-forward re nil t))
(goto-char (match-beginning 1))
(< (string-to-number (match-string 1)) ww)))
(cond
((not match)
(goto-char (point-max))
(or (bolp) (newline))
(insert (format "*** WW%02d\n" ww)))
((= (string-to-number (match-string 1)) ww)
(goto-char (point-at-bol)))
(t
(beginning-of-line 1)
(insert (format "*** WW%02d\n" ww))))))
(defun bnb/insert-weekly-time-sheet ()
"Generated and insert a weekly time sheet generated from the default Org Agenda."
(with-temp-buffer
(insert
(concat "#+BEGIN: clocktable :maxlevel 3 :scope agenda-with-archives :block lastweek :fileskip0 t :properties (\"Score\") :indent nil \n"
"#+TBLFM: $6='(org-clock-time% @2$4 $3..$5);%.1f::@2$2=vsum(@3$2..@>$2)\n"
"#+END:\n\n"))
(goto-char (point-min))
(org-update-dblock)
(buffer-substring (point-min) (point-max))))
(defun bnb/insert-weekly-clocking ()
"Insert the weekly clocking clocking data."
(let ((year (number-to-string (nth 2 (calendar-gregorian-from-absolute (org-today)))))
(ww (bnb/workweek)))
(goto-char (point-min))
(goto-char (cdr (org-id-find "clocking")))
(bnb/find-year-create (string-to-number year))
(bnb/find-ww-create ww)))
This is vestigal content from my Intel days and this generates their idea of a work week number.
(with-after-elpaca-init
(progn
(defun bnb/workweek ()
"Return the current workweek number."
(interactive)
(string-to-number
(format-time-string "%W" (current-time))))
(defun bnb/workweek-string ()
"Convert the current workweek into a string.
The string is of the format WW##."
(interactive)
(concat "WW"
(number-to-string
(bnb/workweek))))
(require 'calendar)
(defun bnb/workweek-from-gregorian (&optional date)
"Calculate the workweek from the Gregorian calendar."
(let* ((date (or date (calendar-current-date)))
(year (calendar-extract-year date))
(fst (calendar-day-of-week (list 1 1 year)))
(x (if (>= fst 4)1 0)))
(+ x
(car
(calendar-iso-from-absolute
(calendar-absolute-from-gregorian date))))))
(setq calendar-week-start-day 1
calendar-intermonth-text
'(propertize
(format "%2d"
(bnb/workweek-from-gregorian (list month day year)))
'font-lock-face 'font-lock-function-name-face))))
Similar to movement, editing happens every day, so I use a few customizations to make it nice.
I really dislike the multi-frame mode of ediff
. It is confusing
to use and really messes up my dwm usage. By explicitly setting
the following setting, it forces ediff
to use only one
frame.
(setq ediff-window-setup-function 'ediff-setup-windows-plain
ediff-diff-options "-w"
ediff-split-window-function 'split-window-horizontally)
Now the control window will be a small window instead of a separate frame.
This interface is a mix of an example on the hydra wiki and my own additions.
I think that the key thing is remembering to not have this affect all cursors when prompted. Otherwise, it seems, the cursors are duplicated in strange ways.
(use-package multiple-cursors
:ensure t
:bind
("H-m" . hydra-mc/body)
("C-x m" . hydra-mc/body)
("s-<mouse-1>" . mc/add-cursor-on-click)
("C-x M" . compose-mail)
:config
(pretty-hydra-define hydra-mc (:hint nil :title " Multiple cursors" :quit-key "q")
("Down"
(("n" mc/mark-next-like-this "Mark next line")
("N" mc/skip-to-next-like-this "Skip next line")
("M-n" mc/unmark-next-like-this "Unmark line going down"))
"Up"
(("p" mc/mark-previous-like-this "Mark previous line")
("P" mc/skip-to-previous-like-this "Skip previous line")
("M-p" mc/unmark-previous-like-this "Unmark line going up"))
"Mark many"
(("l" mc/edit-lines "Convert region")
("a" mc/mark-all-like-this-dwim :exit t "Mark all like selection")
("g" mc/mark-all-in-region-regexp :exit t "Mark regexp in region")
("r" mc/mark-sgml-tag-pair :exit t "Mark tag pair")
("x" mc/mark-more-like-this-extended "Extended marking"))
"Special"
(("1" mc/insert-numbers "Insert numbers")
("^" mc/sort-regions "Sort regions")
("|" mc/vertical-align "Vertially align")
("A" mc/insert-numbers "Insert letters")))))
Emacs regular expressions are not the easiest to use out of the box. Emacs now has regexp-builder to assist you in building the correct regexp as you type.
However, to complicate matters, there are five different syntaxes
of regular expression that the builder can use. The string
syntax is what I tend to use most in searching and replacing, so I
will make that my default.
(setq reb-re-syntax 'string)
Key Binding | Meaning |
---|---|
C-c TAB | Switch syntax |
C-c C-e | Sub-expression mode (show matching groups) |
C-c C-s/r | Search forward/backward |
C-c C-w | Copy regexp to kill ring |
C-c C-q | Quit the builder |
Be sure to consult the syntax of regular expressions to learn more about the weird backslashing.
Taken from http://mbork.pl/2015-01-10_A_few_random_Emacs_tips, this setting makes a file executable if it’s a script.
(add-hook 'after-save-hook
'executable-make-buffer-file-executable-if-script-p)
For view-only buffers rendering content, it is useful to have them
auto-revert
in case of changes.
(add-hook 'doc-view-mode-hook 'auto-revert-mode)
(add-hook 'image-mode 'auto-revert-mode)
Emacs does a good job with images, so any particular preferences are handled in this section.
Register image file types that can be handled by ImageMagick. Note that Emacs needs to be compiled with ImageMagick support for this to do anything.
(with-after-elpaca-init
(when (fboundp 'imagemagick-register-types)
(imagemagick-register-types)))
The minor modes can be used in a variety of situations to enhance the editing experience overall.
These modes help present reference material.
Using Helpful enables a better help buffer by providing a more organized screen with contextual information and linked references.
(use-package helpful
:ensure t
:bind
("C-h K" . helpful-key)
("C-h v" . helpful-variable)
("C-h f" . helpful-function)
("C-h x" . helpful-command)
("C-h z" . helpful-macro)
("C-h ." . helpful-at-point))
This helpful little package makes it easy to remember emacs
prefixed commands. Start typing a prefix such as C-x
after a brief
delay, the options for any following commands are shown.
I am using a setup that tries the right side of emacs first, and punts to a bottom window if there is not enough room.
(use-package which-key
:defer t
:ensure t
:delight which-key-mode
:init
(which-key-mode)
(which-key-setup-side-window-right-bottom)
:custom
(which-key-max-description-length 60))
To use the online dictionary at dict.org, set dictionary-server
accordingly. Then swap around keybindinds such that this is an easy
deafult, but the OSX version isn’t far away.
One of the cooler features of this mode is that the dict.org server has Webster’s 1913 dictionary.
(use-package dictionary
:init
(setq dictionary-server "dict.org")
:bind (("C-c d" . dictionary-search)
("C-c D" . osx-dictionary-search-word-at-point)))
Search Dictionary.app
from the comfort of an Emacs buffer.
(use-package osx-dictionary
:ensure t
:bind
("C-c d" . osx-dictionary-search-word-at-point)
("C-c i" . osx-dictionary-search-input))
This section covers minor modes that personalize and improve the editing experience.
Update: Turning this off for now to see if I really use it for just text modes.
Try to keep any prefixed elements of the first line for paragraph filling.
(use-package filladapt
:delight " ▦"
:disabled t
:ensure t
:commands filladapt-mode
:init (setq-default filladapt-mode t)
:hook ((text-mode . filladapt-mode)
(org-mode . turn-off-filladapt-mode)
(prog-mode . turn-off-filladapt-mode)))
Dim everything except for the thing-at-point. Improves focus when reading code and text.
(use-package focus
:ensure t
:bind
("C-c f" . focus-mode)
("C-c F" . focus-read-only-mode))
CUA has a primary feature of enabling cut, copy, paste and undo shortcuts compatible with most applications, but I leave that part disabled and prefer the normal emacs bindings.
What I do enjoy about CUA are the rectangle restures and that is why I enable it.
(with-after-elpaca-init
(progn
(cua-mode t)
(setq cua-enable-cua-keys nil)))
There are two main binding types: CUA Rectangles and CUA Global Mark.
These take place with an active rectangle. To start/cancel a
rectangle use C-RET
.
Keys | Function |
---|---|
M-<arrow> | Move rectangle overlay |
C-<SPACE> | Activate region bounded by rectangle |
M-a | Align all words at the left edge |
M-b | Fill rectangle with blanks (tabs and spaces) |
M-c | Closes the rectangle by removing left edge blanks |
M-f | Fills the rectangle with a single character (prompt) |
M-i | Increases number found on each line of rectangle |
M-k | Kills the rectangle as normal multi-line kill |
M-l | Downcases the rectangle |
M-m | Copies the rectangle for normal multi-line paste |
M-n | Fills each line with increasing numbers (prompt) |
M-o | Opens the rect by moving hilighted text right and filling with blanks |
M-p | Toggles virtual straight rectangle edges |
M-P | Inserts tabs and spaces to make real straight edges |
M-q | Performs text filling on the rectangle |
M-q | Performs text filling on the rectangle |
M-r | Replaces REGEXP (prompt) by STRING (prompt) in rectangle |
M-R | Reverse the lines in the rectangle |
M-s | Fills each line of the rectangle with the same STRING (prompt) |
M-t | Performs text fill of the rectangle with TEXT (prompt) |
M-u | Upcases the rectangle |
M-<VBar> | Runs shell command on rectangle |
M-' | Restricts rectangle to lines with CHAR (prompt) at left column |
M-/ | Restricts rectangle to lines matching REGEXP (prompt) |
C-? | Shows a brief list of the above commands. |
M-C-<UP/DOWN> | Scrolls the lines INSIDE the rectangle up/down |
The global mark feature enables a target the receives any typed/copied/killed text from any buffer (even the current one).
Key | function |
---|---|
<ch> | All characters (including newlines) you type are inserted at the global mark! |
C-x | If you cut a region or rectangle, it is automatically inserted at the global mark, and the global mark is advanced. |
C-c | If you copy a region or rectangle, it is immediately inserted at the global mark, and the global mark is advanced. |
C-v | Copies a single character to the global mark. |
C-d | Moves (i.e. deletes and inserts) a single character to the global mark. |
<BACKSPACE> | deletes the character before the global mark |
<DELETE> | deletes the character after the global mark. |
S-C-space | Jumps to and cancels the global mark. |
C-u S-C-space | Cancels the global mark (stays in current buffer). |
<TAB> | Indents the current line or rectangle to the column of the global mark. |
While looking for a way to store the filename in the clipboard, I ran across easy-kill. Not only will it grab the filename, but provides ways to grab all sorts of fun things.
(use-package easy-kill
:bind ("M-w" . easy-kill)
:ensure t)
The way the binding works is as a prefix key that also tries a default “kill” and replaces kill-ring-save. The thing at point is saved to the kill ring. The following table shows the details.
Key | Saves at point |
---|---|
M-w w | word |
M-w s | sexp |
M-w l | list |
M-w d | defun |
M-w D | defun name |
M-w f | filename |
M-w b | buffer file name or default directory |
There are also modifiers to treat how the saved text is handled.
Modifier | Effect |
---|---|
@ | append to previous kill |
C-w | kill selection |
+ , - , 1..9 | expand/shrink selection |
0 | shrink selection to initial size |
<SPC> | cycle through easy-kill-alist |
C-<SPC> | turn selection into active region |
C-g | abort |
? | help |
I like to have undo navigation. Vundo gives a nice mini interface (git-style) to move around undo history.
(use-package vundo
:bind
("C-x u" . vundo)
:custom
(vundo-glyph-alist vundo-unicode-symbols))
When in the undo mode, some keys help with navigation.
Key | Effect |
---|---|
f | go forward |
b | go backward |
n | go to the node below at branch point |
p | go to the node above |
a | go back to last branch |
e | go to the end of the branch |
l | go to last saved node |
r | go to next saved node |
m | mark current node for diff |
u | unmark marked node |
d | show a diff |
q | quit (C-g also works) |
Easily one of my favorite packages, this is a nice way to expand selections to semantic regions. Read more on https://github.com/magnars/expand-region.el.
(use-package expand-region
:ensure t
:bind ("C-=" . er/expand-region))
For getting completions from bibliographic data, Citar links things together.
(use-package citar
:commands (citar-capf-setup)
:ensure t
:bind
(:map org-mode-map ("C-c b" . #'org-cite-insert))
:custom
(citar-bibliography bnb/biblio)
(org-cite-global-bibliography bnb/biblio)
(org-cite-insert-processor 'citar)
(org-cite-follow-processor 'citar)
(org-cite-activate-processor 'citar)
:hook
(LaTeX-mode . citar-capf-setup)
(org-mode . citar-capf-setup))
(use-package citar-embark
:ensure t
:after (citar embark)
:no-require
:config (citar-embark-mode)
:custom
(citar-at-point-function 'embark-act))
(use-package citar-org-roam
:ensure t
:after (citar org-roam)
:config
(add-to-list 'org-roam-capture-templates
'("n" "Literature note" plain
"%?"
:target
(file+head
"%(expand-file-name (or citar-org-roam-subdir \"\") org-roam-directory)/${citar-citekey}.org"
"#+title: ${citar-citekey} (${citar-date}). ${note-title}.\n#+created: %U\n#+last_modified: %U\n\n")
:unnarrowed t))
(citar-org-roam-mode))
Introduced in emacs 29, tree sitter transforms code into a concrete syntax tree. Read up on how to get started with tree-sitter
Automatically install treesit grammars
(use-package treesit-auto
:defer t
:ensure t
:custom
(treesit-auto-install 'prompt)
:config
(treesit-auto-add-to-auto-mode-alist 'all)
(global-treesit-auto-mode))
Emacs is fantastic for interfacing with version control systems. For git, it may have the best interface.
Magit is a git interface for Emacs.
Here I set a global key for magit-status
. Think ‘G’ looks like 6.
(use-package transient)
(use-package magit
:ensure t
:after transient
:bind ("<f6>" . magit-status)
:custom
(magit-last-seen-setup-instructions "1.4.0"))
This magit release warns about auto-revert of buffers. This is
turned on by default and I will keep that setting. To turn off
the magit warning, I set magit-last-seen-setup-instructions
to
1.4.0 as shown above.
Git is different than Github and Gitlab. Forge provides the right interface to work with both of these forges.
(use-package forge
:after magit
:ensure (forge :host github :repo "magit/forge")
:commands (forge-pull))
Somewhere along the line, smerge
was added to native version
control support. To facilitate editing merge conflicts, this hydra
helps me do the work.
(use-package smerge
:ensure nil
:bind
(:map smerge-mode-map ("C-c ^ h" . hydra-smerge/body))
:mode-hydra
(hydra-smerge (:color amaranth :title " SMerge" :quit-key "q")
("Selection"
(("a" smerge-keep-all "Keep all")
("b" smerge-keep-base "Keep base")
("m" smerge-keep-mine "Keep mine")
("o" smerge-keep-other "Keep other")
("r" smerge-resolve "Keep mine"))
"Movement"
(("n" smerge-next "Next conflict")
("p" smerge-previous "Previous conflict")))))
Authors can always use that little bit of extra help to ensure the prose is right from the beginning.
To avoid weaslewords, passive voice, and accidental duplicates, employ Writegood.
(use-package writegood-mode
:ensure t
:bind
("C-c g" . writegood-mode)
("C-c C-g g" . writegood-grade-level)
("C-c C-g e" . writegood-reading-ease))
This site has an interesting suggestion on how to use aspell
for
CamelCase spell checking.
(with-after-elpaca-init
(progn
(cond
((executable-find "aspell")
(setq ispell-program-name (executable-find "aspell")
ispell-extra-args '("--sug-mode=ultra" "--lang=en_US")))
(t (setq ispell-program-name nil)
(message "No aspell found!")))
(bind-key "H-$" 'ispell-word)))
To get a complete, robust analysis of writing, Proselint can be configured to work as a checker for flycheck.
Note that the executable needs to be installed on the system and is not automatically provided.
(with-eval-after-load "flycheck-mode"
(flycheck-define-checker proselint
"A linter for prose"
:command ("proselint" source-inplace)
:error-patterns
((warning line-start (file-name) ":" line ":" column ": "
(id (one-or-more (not (any " "))))
(message (one-or-more not-newline)
(zero-or-more "\n" (any " ") (one-or-more not-newline)))
line-end))
:modes (text-mode markdown-mode gfm-mode org-mode))
(add-to-list 'flycheck-checkers 'proselint))
Handling buffers is central to an effective Emacs experience. This section adds in the tools to make management easy.
This mode looks at midnight and kills any inactive buffers (keeping things tidy). By default, inactive means is any buffer untouched for three days.
(use-package midnight
:ensure nil
:defer 10)
Use ibuffer instead of list-buffers for buffer management. The
most visible difference being the coloring that ibuffer
uses.
I also squash any empty groups from being displayed and add hooks to automatically set the filter groups and update contents.
(use-package ibuffer
:ensure nil
:bind
("C-x C-b" . ibuffer)
:custom
(ibuffer-show-empty-filter-groups nil)
:hook
(ibuffer-mode . (lambda ()
(ibuffer-auto-mode 1)
(ibuffer-switch-to-saved-filter-groups "Standard"))))
The buffer list splits into arbitrary groups for easier
management. Below I create an “Org” group for org-mode
buffers.
(setq ibuffer-saved-filter-groups
'(("Standard"
("Emacs" (or (filename . ".*bnb-emacs.*")
(mode . emacs-lisp-mode)))
("Org" (mode . org-mode))
("Magit" (name . "\*magit"))
("Mail" (or (mode . message-mode)
(mode . mail-mode)))
("HTML" (mode . html-mode))
("Help" (or (name . "\*Help\*")
(name . "\*Apropos\*")
(name . "\*info\*"))))))
The ibuffer-vc Package provides groups according to version
control sets. Here I setup a small keybinding (/ v
) to get to the
filtered vc groups. The keys / R
will go back to the standard
view.
(use-package ibuffer-vc
:ensure t
:bind
(:map ibuffer-mode-map
("/ ;" . ibuffer-vc-set-filter-groups-by-vc-root)))
When editing files with the same name, but different location, a
unique identifier (based on path) is preferred over a number. The
format below shows the buffername as <filename>:<parent directory>
.
(use-package uniquify
:ensure nil
:defer 10
:config
(setq uniquify-buffer-name-style 'post-forward
uniquify-separator ":"))
For file-backed buffers, reveal the file in OSX finder with this binding.
(use-package reveal-in-osx-finder
:ensure t
:bind ("C-c z" . reveal-in-osx-finder))
The minor modes for development deal mainly with parenthenses and structured editing.
This check has saved me from a broken configuration file many times. I highly recommend.
(add-hook 'after-save-hook 'check-parens nil t)
While developing, documentation is nice to have handy and automatic.
(use-package eldoc
:ensure nil
:hook
(prog-mode . turn-on-eldoc-mode)
(ielm-mode . turn-on-eldoc-mode)
:custom (eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly)
:config
(eldoc-add-command-completions "paredit-"))
Use the built-in `eglot` functionality.
(use-package eglot
:ensure nil
:demand t)
In Emacs devel, there is also an `eglot-x` that will be available, but it is tracking some development versions for now, so I have left it out.
I added paredit-mode
to several of the lisp modes that follow.
(use-package paredit
:ensure t
:delight " 🍐"
:hook
(emacs-lisp-mode . paxedit-mode)
(clojure-mode . paxedit-mode)
:commands (paredit-mode))
Maybe even more power for lisp coding? Paxedit repo
(use-package paxedit
:ensure t
:delight " ꁀ"
:hook
(emacs-lisp-mode . paxedit-mode)
(clojure-mode . paxedit-mode)
:bind
(:map paxedit-mode-map
("M-<right>" . paxedit-transpose-forward)
("M-<left>" . paxedit-transpose-backward)
("M-<up>" . paxedit-backward-up)
("M-<down>" . paxedit-backward-end)
("M-b" . paxedit-previous-symbol)
("M-f" . paxedit-next-symbol)
("C-%" . paxedit-copy)
("C-&" . paxedit-kill)
("C-*" . paxedit-delete)
("C-^" . paxedit-sexp-raise)
("M-u" . paxedit-symbol-change-case)
("C-@" . paxedit-symbol-copy)
("C-#" . paxedit-symbol-kill)))
In deeply nested structures (I’m looking at you lisp), automatically coloring the matching delimiter can speed up understanding and refactoring.
For a lighter-weight alternative, check out show-paren-mode.
(use-package rainbow-delimiters
:ensure t
:hook (prog-mode . rainbow-delimiters-mode))
There are a couple of built-in features that help with organization. (Outside of org-mode, that is)
http://emacswiki.org/emacs/BookMarks
Keystroke | Action |
---|---|
C-x r m | Set a bookmark |
C-x r b | Jump to a bookmark |
C-x r l | List your bookmarks |
M-x bookmark-delete | Delete bookmark by name |
The settings below auto-save bookmarks, adds a fringe marker on the current line when setting/jumping, and confirms bookmark deletion.
(setq bookmark-save-flag t
bookmark-set-fringe-mark t
bookmark-menu-confirm-deletion t)
The built-in emacs package project
replaces the projectile
functionality. Use C-x p p
to navigate to a project and get started.
Useful key mappings
Binding | Function |
---|---|
C-x p p | Find project |
C-x p f | Find file in project |
C-x p b | Find buffer in project |
C-x p e | Eshell in project |
This section holds the details for major modes. These are interfaces in their own right.
Finding files in the minibuffer is easy with the completion frameworks above, but Emacs also functions very well as a fully fledged file manager.
The built-in directory editor, dired, can be customized to a great degree. The following sections walk us through my particular preferences.
First, add the basic dired settings.
(setq dired-kill-when-opening-new-dired-buffer t
dired-mark-region t)
The first setting, dired-kill-when-opening-new-dired-buffer keeps
dired
from littering little visited folder buffers along the way.
Marking commands can now act on regions with dired-mark-region
set to t
.
These are part of the dired-hacks repository.
(use-package dired-hacks-utils
:defer t
:ensure t)
This first package provides dired-filter-mode giving some very
handy ways to filter the view. Enable it with C-c C-d f
and then
get to the commands with C-c C-f
.
(use-package dired-filter
:defer t
:hook (dired-mode . dired-filter-mode)
:ensure t
:bind
("C-c C-d f" . dired-filter-mode)
:bind-keymap
("C-c C-f" . dired-filter-map))
This next addition colorizes files in dired accorting to type or
the chmod
bits.
(use-package dired-rainbow
:ensure t
:config
(progn
(dired-rainbow-define html "#eb5286" ("css" "less" "sass" "scss" "htm" "html" "jhtm" "mht" "eml" "mustache" "xhtml"))
(dired-rainbow-define xml "#f2d024" ("xml" "xsd" "xsl" "xslt" "wsdl" "bib" "json" "msg" "pgn" "rss" "yaml" "yml" "rdata"))
(dired-rainbow-define document "#9561e2" ("docm" "doc" "docx" "odb" "odt" "pdb" "pdf" "ps" "rtf" "djvu" "epub" "odp" "ppt" "pptx"))
(dired-rainbow-define markdown "#ffed4a" ("org" "etx" "info" "markdown" "md" "mkd" "nfo" "pod" "rst" "tex" "textfile" "txt"))
(dired-rainbow-define database "#6574cd" ("xlsx" "xls" "csv" "accdb" "db" "mdb" "sqlite" "nc"))
(dired-rainbow-define media "#de751f" ("mp3" "mp4" "MP3" "MP4" "avi" "mpeg" "mpg" "flv" "ogg" "mov" "mid" "midi" "wav" "aiff" "flac"))
(dired-rainbow-define image "#f66d9b" ("tiff" "tif" "cdr" "gif" "ico" "jpeg" "jpg" "png" "psd" "eps" "svg"))
(dired-rainbow-define log "#c17d11" ("log"))
(dired-rainbow-define shell "#f6993f" ("awk" "bash" "bat" "sed" "sh" "zsh" "vim"))
(dired-rainbow-define interpreted "#38c172" ("py" "ipynb" "rb" "pl" "t" "msql" "mysql" "pgsql" "sql" "r" "clj" "cljs" "scala" "js"))
(dired-rainbow-define compiled "#4dc0b5" ("asm" "cl" "lisp" "el" "c" "h" "c++" "h++" "hpp" "hxx" "m" "cc" "cs" "cp" "cpp" "go" "f" "for" "ftn" "f90" "f95" "f03" "f08" "s" "rs" "hi" "hs" "pyc" ".java"))
(dired-rainbow-define executable "#8cc4ff" ("exe" "msi"))
(dired-rainbow-define compressed "#51d88a" ("7z" "zip" "bz2" "tgz" "txz" "gz" "xz" "z" "Z" "jar" "war" "ear" "rar" "sar" "xpi" "apk" "xz" "tar"))
(dired-rainbow-define packaged "#faad63" ("deb" "rpm" "apk" "jad" "jar" "cab" "pak" "pk3" "vdf" "vpk" "bsp"))
(dired-rainbow-define encrypted "#ffed4a" ("gpg" "pgp" "asc" "bfe" "enc" "signature" "sig" "p12" "pem"))
(dired-rainbow-define fonts "#6cb2eb" ("afm" "fon" "fnt" "pfb" "pfm" "ttf" "otf"))
(dired-rainbow-define partition "#e3342f" ("dmg" "iso" "bin" "nrg" "qcow" "toast" "vcd" "vmdk" "bak"))
(dired-rainbow-define vc "#0074d9" ("git" "gitignore" "gitattributes" "gitmodules"))
(dired-rainbow-define-chmod executable-unix "#38c172" "-.*x.*")
(dired-rainbow-define-chmod directory "#6cb2eb" "d.*")))
Instead of filter mode, we can use two functions to narrow the
dired
buffer to only the matches. The flavors of filterring are
fuzzy or regexp.
(use-package dired-narrow
:defer t
:bind (:map dired-mode-map
("C-c C-d n" . dired-narrow-fuzzy)
("C-c C-d r" . dired-narrow-regexp))
:ensure t)
In the case of deep folder structures without any intermediate files (files only in the leaves), this mode helps collapse the view to be easier to navigate.
Per the documentation, this will be especially useful in
directories such as ~/.config/foo/config
.
(use-package dired-collapse
:defer t
:bind
("C-c C-d c" . dired-collapse-mode)
:ensure t)
Also, there is a nice faculty to run an external command on a given file with ==!==.
Neotree is a file sidebar for navigation. Dired has a superior
interface for interacting with files, but Neotree
offers the tree
stucture view for the times you need to see the whole folder tree.
(use-package neotree
:ensure t
:commands (neotree)
:bind ("H-t" . neotree-toggle)
:config
(setq neo-theme (if (display-graphic-p) 'icons 'arrow)))
These days, one of my favorite uses of the one true editor is to author prose and scientific writing. These major modes provide the tools to make this easy and fun.
Using TeX systems is easy with AUCTeX. To learn everything, read the AUCTeX Manual.
The following block ensures that AUCTeX builds and loads
correctly. The main settings deal with setting up PDF
output and
the xetex
engine.
(use-package auctex
:after org
:ensure (auctex
:pre-build (("./autogen.sh")
("./configure" "--without-texmf-dir" "--with-lispdir=.")
("make")))
:mode (("\\.tex\\'" . LaTeX-mode)
("\\.tex\\.erb\\'" . LaTeX-mode)
("\\.etx\\'" . LaTeX-mode))
:hook ((LaTeX-mode . flyspell-mode)
(LaTeX-mode . LaTeX-math-mode)
(LaTeX-mode . auto-fill-mode)
(LaTeX-mode . orgtbl-mode)
(doc-view-mode . auto-revert-mode))
:config
(setq TeX-auto-untabify t
TeX-auto-save t
TeX-save-query nil
TeX-parse-self t
TeX-output-view-style
(if (eq system-type 'windows-nt)
(quote
(("^pdf$" "." "SumatraPDF.exe -reuse-instance %o")
("^html?$" "." "start %o")))
(quote
(("^pdf$" "." "evince -f %o")
("^html?$" "." "start %o"))))
TeX-command-extra-options "-shell-escape"
TeX-PDF-mode 1
TeX-engine 'xetex)
(setq-default TeX-master nil)
(setq-default TeX-global-PDF-mode 1)
(add-to-list 'org-latex-packages-alist
'("" "tikz" t))
(add-to-list 'org-latex-packages-alist
'("" "minted" t))
(setq org-latex-create-formula-image-program 'imagemagick)
(eval-after-load "preview"
'(add-to-list 'preview-default-preamble "\\PreviewEnvironment{tikzpicture}" t)))
Let me walk through some of the settings.
Parse-self and auto-save will parse the file on load and save respectively. Untabify will remove tabs (real ones) before saving.
I also have a default of TeX-master
set to nil
. I used to have it
set to “master” as recommended in the documentation, but I had bad
results for LaTeX files generated on the fly.
For viewing the output, I can specify the command to use on the files generated in the process. However, the programs differ on GNU/Linux and Windows, so I have per-platform settings.
There are two settings that add the tikz
and minted
packages to
org-mode
exports.
RefTeX provides navigation, easy references, easy citations and integrates well into AUCTeX. Find all of the details in the RefTeX Manual.
Automatically turn it on in LaTeX modes.
(add-hook 'LaTeX-mode-hook 'turn-on-reftex)
Keystroke | Function |
---|---|
C-c = | Show TOC and jump to sections |
C-c ( | Insert a label |
C-c ) | Reference a label |
C-c [ | Insert a citation (from BibTex db) |
C-c < | Index entry |
C-c > | View index |
C-c & | View crossref |
The document standard that is ubiquitous, but typically authored through some processing tool. This section helps wtih the real hands-on editing of HTML (and other similarily structued) documents.
In order to play nicely with HTML+, it needs to be added to the filters of impatient mode.
(use-package impatient-mode
:ensure t
:mode "\\.html\\'"
:bind
("C-x C-h s" . httpd-start)
("C-x C-h x" . httpd-stop)
("C-x C-h d" . httpd-serve-directory)
:config
(add-to-list 'imp-default-user-filters '(mhtml-mode . nil)))
To use impatient mode, you’ll first want to call httpd-start and then navigate to http://localhost:8080/imp to see the rendered buffers.
Emmet mode allows for terse description of nested elements. There is great documentation on the approach at emmet.io.
(use-package emmet-mode
:ensure t
:commands (emmet-mode)
:hook ((web-mode . emmet-mode)
(sgml-mode . emmet-mode)
(css-mode . emmet-mode)))
Everything can’t be as nice as org-mode. Oh well.
(use-package markdown-mode
:ensure t
:commands (markdown-mode gfm-mode)
:mode (("README\\.md\\'" . gfm-mode)
("\\.md\\'" . markdown-mode)
("\\.markdown\\'" . markdown-mode))
:init (setq markdown-command "multimarkdown"))
The one feature I cannot do without. Let’s set up some basics.
(use-package org
:ensure (org :type git :repo "https://git.savannah.gnu.org/git/emacs/org-mode.git" :tag "release_9.7.12")
:delight (org-mode "🦄" :major)
:mode ("\\.org\\(_archive\\)?\\'" . org-mode)
:bind
("C-c t" . orgtbl-mode)
("C-c l" . org-store-link)
("C-c r" . org-capture)
("C-c a" . org-agenda)
("<f12>" . org-agenda)
("H-z" . org-agenda)
("<apps>" . org-agenda)
("<f9> g" . org-clock-goto)
("<f9> i" . org-clock-in)
("<f9> o" . org-clock-out)
(:map org-mode-map
("M-i" . org-toggle-inline-images)
("C-c ," . org-insert-structure-template)
("C-c ," . org-insert-structure-template)
("M-o" . ace-link-org))
:hook
(org-babel-after-execute . (lambda () (org-display-inline-images t t))))
This block installs org-mode along with some bindings.
Binding | Effect |
---|---|
C-c t | Edit tables in any mode |
C-c l | Store a link to thing at point |
C-c r | Dispatch org capture |
<f12> | Open org agenda |
H-z | Open org agenda |
<apps> | Open org agenda |
<f9> g | Goto open org clock |
<f9> i | Org clock in |
<f9> o | Org clock out |
There are a set of contributed packages that install nicely together. This code block ensures that the libraries are available.
(use-package org-contrib
:after org
:ensure t)
I add _archive
to the list of known org files. This alternative
extensions correctly identifies org archives (.org_archive
).
This mode is set above with use-package
.
There are two hooks to consider. Theone initialized in Orgmode toggles on the inline images.
The next hook just saves the org files opened before exiting emacs – just in case.
(with-eval-after-load 'org
(add-hook 'bnb/kill-emacs-hooks 'org-save-all-org-buffers 'append))
Using org-mode efficiently for task management is best done with
speed keys. This are in effect when the cursor is on the first *
of a headline. And they come with an easy cheat-sheet by typing
?
. I enable this feature and add some of my own commands.
(with-eval-after-load 'org
(setq org-use-speed-commands t
org-speed-commands
(append org-speed-commands
'(("BNB Additions")
("0" . delete-window)
("1" . delete-other-windows)
("2" . split-window-vertically)
("3" . split-window-horizontally)
("z" . org-add-note)
("h" . hide-other)
("." . org-save-all-org-buffers)
("/" . org-global-cycle)
("k" . #'(lambda ()
(org-mark-subtree)
(kill-region
(region-beginning)
(region-end))))))))
This is the meat of what Org can do. Keeping track of todo items with due dates, tags, etc. is really powerful. And I get to customize it to suit my needs and my workflow.
The keywords that org uses in the headlines exist as sequences describing the state changes. The sequences describe how you can cycle through the different states. However, I don’t cycle through states and just select them. I find that is better for the large list of possibilities here.
The characters in ()
also allow fast access to these states
described here.
(with-eval-after-load 'org
(setq org-todo-keywords
'((sequence "TODO(t)" "NEXT(n)" "|" "DONE(d!/!)")
(sequence "WAITING(w@/!)" "SOMEDAY(s!)" "|" "CANCELED(c@/!)")
(sequence "CANCELED(c@/!)"))))
Org uses tags on headlines for organization. I don’t currently
use them much. I organize mainly by file with a file tag
specified via in-buffer settings (#+FILETAGS
).
However, a global tag list provides a selection list for the tagging interface. I use ‘project’ as my tag to easily differentiate simple tasks from more complex ones.
(with-eval-after-load 'org
(setq org-tag-alist '(("PROJECT" . ?p))))
The todo interface allows easy selection of states and triggers on certain states to store notes.
Instead of cycling through states (and possibly triggering log entries), I prefer fast entry to jump right to the correct state. I also turn off the S-cursor transitions as state changes to avoid the logging prompts.
(with-eval-after-load 'org
(setq org-use-fast-todo-selection t
org-treat-S-cursor-todo-selection-as-state-change nil))
Upon changing the state of todo items, I can automatically add/remove tags with the following list. It’s a bit lispy, but describes what happens upon entry in the specified state. The state named as a string has tuples of tags and flags. ‘t’ indicates to set the flag, empty means to remove it.
(with-eval-after-load 'org
(setq org-todo-state-tags-triggers
'(("CANCELED" ("CANCELED" . t))
("WAITING" ("WAITING" . t))
("SOMEDAY" ("SOMEDAY" . t))
(done ("WAITING"))
("TODO" ("WAITING") ("CANCELED"))
("NEXT" ("WAITING"))
("DONE" ("WAITING") ("CANCELED")))))
Along with tags and states are priorities. I do not use task priorities myself so I turn them off.
(with-eval-after-load 'org
(setq org-enable-priority-commands nil))
Org allows logging of states. I turn this on to prompt myself for reasons behind specific state changes. There is also a setting to set a different drawer for clocking and logs.
(with-eval-after-load 'org
(setq org-log-done 'note
org-log-redeadline 'time
org-log-reschedule 'time
org-log-into-drawer t
org-drawers '("PROPERTIES" "LOGBOOK" "CLOCK")))
Naturally, some tasks are projects composed of smaller sub-tasks. Org handles this easily. I like to enforce the dependencies of regular todo items and plain checkbox lists. In this way, the overall item cannot change to done without the completion of the sub-tasks.
(with-eval-after-load 'org
(setq org-enforce-todo-checkbox-dependencies t
org-enforce-todo-dependencies t))
Because of the previous enforcement of state, I can also automatically infer when a parent state is complete. The following code marks the parent complete once the sub-tasks are all done.
(with-eval-after-load 'org
(defun org-summary-todo (n-done n-not-done)
"Switch entry to DONE when all sub-entries are done, to TODO otherwise."
(let (org-log-done org-log-states)
(org-todo (if (= n-not-done 0) "DONE" "TODO"))))
(add-hook 'org-after-todo-statistics-hook 'org-summary-todo))
Capturing is crucial to a task system and in this vein, org is no slouch. The capture templates define what get captured, where it goes, and what the user needs to type.
;; Files
(setq org-default-notes-file "~/Documents/Org/Inbox.org"
bnb/weekly-reports-file "~/Documents/Org/WeeklyReports.org")
;; Default templates
(setq org-capture-templates
`(("t" "Todo" entry
(file ,org-default-notes-file)
"* TODO %?\n %U\n%^{Score}p" :clock-in t :clock-resume t)
("r" "todo (Remember location)" entry
(file org-default-notes-file)
"* TODO %?\n %U\n %a" :clock-in t :clock-resume t)
("n" "Note" entry
(file org-default-notes-file)
"* %? :NOTE:\n %U\n %a\n :CLOCK:\n :END:")
("c" "Capture current TODO mix in table" table-line (file+headline ,bnb/weekly-reports-file "Burndown")
"%(bnb/org-count-tasks-by-status)")
("s" "Capture Weekly Score in table" table-line (file+headline ,bnb/weekly-reports-file "Scores")
"%(bnb/add-weekly-score-table-entry)")
("e" "Capture Weekly time in table" table-line (file+headline ,bnb/weekly-reports-file "Minutes")
"%(bnb/org-time-logged-table-entry)")
("u" "Url" entry (file ,org-default-notes-file)
"* TODO %?\n %U\n\n %(org-mac-chrome-get-frontmost-url)")
("m" "Mail" entry (file ,org-default-notes-file)
"* TODO %?\n %U\n\n %(org-mac-message-get-links \"s\")")))
There are five main capture templates here. The first two store a todo item in my Refile.org file. The only difference is automatic (contextual) link storage in the second case.
The next item simply stores a note. The next for “Weekly Report” is a work in progress. I think that I’ll have to either settle for a proper datetree or write a custom function.
The final item is not for direct use, but through the
org-protocol
interface and org-outlook
usage. This lets me add a
link to an Outlook message on windows. I can then get an email at
work, mark it to store in emacs and quickly get back to the
message later.
These helpers provide functionality used in the capture templates above.
Modified from Sacha Chua, this code get the current mix of tasks in the agenda files. I use this as part of my weekly review for task amount and mix at a glance.
(defun bnb/org-count-tasks-by-status ()
"Create a table entry for the tracking of task mix."
(interactive)
(let ((counts (make-hash-table :test 'equal))
(today (format-time-string "%Y-%m-%d" (current-time)))
values output)
(org-map-entries
(lambda ()
(let ((status (elt (org-heading-components) 2)))
(when status
(puthash status (1+ (or (gethash status counts) 0)) counts))))
"-HOME"
'agenda)
(setq values (mapcar (lambda (x)
(or (gethash x counts) 0))
'("DONE" "TODO" "WAITING" "CANCELLED" "SOMEDAY")))
(setq output
(concat "| " today " | "
(mapconcat 'number-to-string values " | ")
" | "
(number-to-string (apply '+ values))
" | "
(number-to-string
(round (/ (* 100.0 (car values)) (apply '+ values))))
"% |"))
(if (called-interactively-p 'any)
(insert output)
output)))
I also have a helper function to get the score of done tasks closed within the last week. I store this in a table line with year and workweek number.
(defun bnb/add-weekly-score-table-entry ()
"Track my weekly scores in a table."
(let ((score (apply
'+
(org-map-entries
(lambda ()
(string-to-number (or (org-entry-get (point) "Score") "0")))
"/DONE"
'agenda)))
(year (format-time-string "%Y" (current-time)))
(ww (number-to-string (bnb/workweek))))
(format "| %s | %s | %s |" year ww score)))
How about the hours logged last week? Let’s give that a go. Note that if there are no clocked hours for a week, this comes up empty.
(defun bnb/get-clocked-categories-hashtbl (days-ago)
"Get clocktable of categories starting DAYS-AGO"
(let ((clocked-fmt (- 0 days-ago))
(org-fmt (format-time-string "%Y-%m-%d" (time-subtract (current-time) (days-to-time days-ago)))))
(let ((clocktbl (make-hash-table :test 'equal))
(entries (org-ql-select (org-agenda-files) '(clocked :from clocked-fmt)
:action (lambda ()
(list (org-entry-get-with-inheritance "CATEGORY" t)
(org-clock-sum-current-item org-fmt))))))
(dolist (e entries clocktbl)
(puthash (car e) (+ (cadr e)
(or (gethash (car e) clocktbl) 0))
clocktbl)))))
(defun bnb/org-time-logged-table-entry (&optional additional-weeks include-zeros)
"Insert table of minutes per category.
Optionally provide ADDITIONAL-WEEKS to get more history"
(interactive "P")
(let* ((minh (bnb/get-clocked-categories-hashtbl (+ 7 (* 7 (or additional-weeks 0)))))
(today (format-time-string "%Y-%m-%d" (current-time))))
;; Print out table lines
(let ((rows nil))
(maphash
(lambda (k v)
(when (or (> v 0) include-zeros)
(setq rows
(cons (format "| %s | %s | %d |" today k v) rows))))
minh)
(if (called-interactively-p 'any)
(insert (mapconcat 'identity rows "\n"))
(mapconcat 'identity rows "\n")))))
; (bnb/org-time-logged-table-entry 1 t)
Refiling notes is also spectacular with Org. That is what makes it possible for me to simply put every captured item into Refile.org and worry about organization later.
For my setup, I use separate files that hold a singular Tasks headline. Because of that, I turn on caching first.
For the refile targets, I will allow up to 2 levels of search for filing in any of the agenda files. For refiling within the current file, I set the max to five levels. Anything deeper than six levels will exhaust the depth of my thought.
The refiling system is set to confirm
creation of parent
nodes. This allows me to not only refile, but create along the
way for faster organization.
Finally, I set the filenames to be first for refiling.
(setq org-refile-use-cache t
org-refile-targets '((org-agenda-files :maxlevel . 2)
(nil :maxlevel . 5))
org-refile-allow-creating-parent-nodes 'confirm
org-refile-use-outline-path 'file)
Once I have captured and refiled my tasks, I need to remember to do them and see what is on the agenda. The ways to view the tasks at hand are nicely programmable.
Some basic settings control small tidbits in the agenda. I turn on tags in the agenda line, show the logged items for the day, and only show a time grid if a scheduled tasks exists.
(setq org-agenda-show-inherited-tags t
org-agenda-log-mode-items '(clock)
org-agenda-clockreport-parameter-plist '(:link nil :maxlevel 2 :fileskip0 t)
org-agenda-block-separator nil
org-agenda-dim-blocked-tasks nil
org-agenda-inhibit-startup t
org-agenda-breadcrumbs-separator " ❱ ")
Enriching the agenda view is easy with org-super-agenda. I don’t make any default settings here, but override org-super-agenda-groups for each agenda view dispatcher.
(use-package org-super-agenda
:after org
:ensure t
:config
(org-super-agenda-mode))
The examples online provide a good idea of the grouping customization (and ease).
Org-mode can show category icons in some agenda views. The
underlying setting is just an alist
of categories and the icons
to use.
;;;;;;;;;;;;;
;; Org agenda category icons
(with-eval-after-load 'nerd-icons
(setq org-agenda-category-icon-alist
`(("[iI]nbox" ;;
,(list (nerd-icons-octicon "nf-oct-inbox")) nil nil :ascent center)
("[Ww]ork" ;;
,(list (nerd-icons-octicon "nf-oct-organization")) nil nil :ascent center)
("[Pp]ersonal" ;;
,(list (nerd-icons-octicon "nf-oct-home")) nil nil :ascent center)
("[Ii][Cc]" ;;
,(list (nerd-icons-octicon "nf-oct-person")) nil nil :ascent center)
("[Mm]anager" ;;
,(list (nerd-icons-faicon "nf-fa-cogs")) nil nil :ascent center))))
The key to exploring open work are the agenda views. These provide a landscape to list, filter or manipulate tasks. org-agenda-custom-commands defines which views are available by default.
Adapted from org-leuven-agenda-views.txt.
;; Reset everything to nil
(setq org-agenda-custom-commands nil)
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("o" "My Agenda"
((todo "TODO"
((org-agenda-overriding-header "\n⚡ Do Today\n┄┄┄┄┄┄┄┄┄┄")
(org-agenda-remove-tags t)
(org-agenda-prefix-format " %-2i %-15b")
(org-agenda-todo-keyword-format "")))
(agenda ""
((org-agenda-start-day "+0d")
(org-agenda-span 5)
(org-agenda-overriding-header "⚡ Schedule\n┄┄┄┄┄┄┄┄┄┄")
(org-agenda-repeating-timestamp-show-all nil)
(org-agenda-remove-tags t)
(org-agenda-prefix-format " %-3i %-15b %t%s")
(org-agenda-todo-keyword-format " ☐ ")
(org-agenda-current-time-string "⮜┈┈┈┈┈┈┈ now")
(org-agenda-scheduled-leaders '("" ""))
(org-agenda-time-grid (quote ((daily today remove-match)
(0900 1200 1500 1800 2100)
" " "┈┈┈┈┈┈┈┈┈┈┈┈┈")))))))))
In the process section, the goal is to take the incoming items
and process them. There is a menu key, p
, that will open up
the different views.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("p" . "PROCESS...") t))
The first view grabs anything tagged inbox
. In my setup, my
default notes file does have a filetag of inbox
, so all of
those will also show up in this view.
The nice part is that I can have other agenda files with inboxes that need addressed This helps them stay in the forefront of processing.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("pp" "All TODOs in Inboxen"
((org-ql-block '(and (tags "inbox") (or (todo) (done)))
((org-ql-block-header "Inboxed tasks")
(org-agenda-overriding-header "List of all TODO tasks in Inbox")
(org-agenda-sorting-strategy '(priority-down))
(org-super-agenda-groups '((:auto-category t))))))) t))
I try to put effort estimates on all of my tasks. As part of
processing, the pe
view lists tasks without effort. Typically I
then edit with org-columns (C-c C-x C-c
).
In the column view, navigating to the effort column and hitting the right number will set the effort to one of the predefined settings (found in org-global-properties).
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("pe" "No Estimates"
((org-ql-block '(and (not (effort)) (todo))
((org-agenda-overriding-header "Lacking ESTIMATES")
(org-agenda-sorting-strategy '(priority-down))
(org-super-agenda-groups '((:auto-category t))))))) t))
This view (reachable via ps
) is similar to the previous one,
only it finds tasks without scores. This helps me process them
in the same way – ensure everthing that can have a score,
does.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("ps" "No Score"
((org-ql-block '(and (not (property "SCORE")) (or (todo) (done)))
((org-agenda-overriding-header "Lacking SCORE")
(org-agenda-sorting-strategy '(priority-down)))))) t))
The archivable view pa
is also similar to the previous one,
only it finds tasks that are complete and ready to be refiled
or archived.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("pa" "Archivable"
((agenda "DONE|PROJECTDONE"
((org-agenda-clockreport-mode t)
(org-agenda-start-day "-7")
(org-agenda-overriding-header "PAST WEEK")
(org-agenda-prefix-format " %?-11t %i %-12:c% s")
(org-agenda-show-log 'clockcheck)))
(org-ql-block '(done)
((org-agenda-overriding-header "Completed Tasks")
(org-agenda-sorting-strategy '(priority-down))
(org-super-agenda-groups '((:auto-category t)))))))))
Finally, this view shows the items tagged someday
. As process
goes, this could be part of review, but I like it better as a
processing step.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("pd" "Somday items" tags-todo "SOMEDAY"
((org-agenda-overriding-header "SOMEDAY tasks")
(org-agenda-sorting-strategy '(priority-down)))) t))
The focus section builds up views to help focus on tasks. The
dispatcher starts with an f
.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("f" . "FOCUS...") t))
The today focus view unfolds like a compass guiding through the day’s tasks, presenting a constellation of sections aimed at pinpointing the most crucial endeavors to tackle next.
In the calendar section, a mosaic of diary entries and timestamps for today awaits exploration, offering a glimpse into the rhythm of the day.
The inbox section beckons with its array of unscheduled top-level items, each a potential gem awaiting processing. Here lie tasks in need of scheduling, refining, or simply bringing to completion.
For those with deadlines looming, the due today and overdue sections serve as a stark reminder, listing tasks teetering on the edge of time. They demand attention, beckoning to be brought to fruition.
Meanwhile, the scheduled section offers a glimpse into the near future, showcasing the tasks slated for today, like eager performers awaiting their cue.
Lastly, the completed section stands as a testament to progress, a growing archive of triumphs marked by tasks conquered. As the week unfolds, this section burgeons, reflecting the journey traversed and milestones achieved.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
`("f." "Today"
((agenda ""
((org-agenda-entry-types '(:timestamp :sexp))
(org-agenda-overriding-header
(concat "CALENDAR Today "
(format-time-string "%a %d" (current-time))))
(org-agenda-span 'day)))
(tags-todo "LEVEL=1+inbox"
((org-agenda-overriding-header "INBOX (Unscheduled)")))
(tags-todo "DEADLINE<\"<+1d>\"+DEADLINE>\"<-1d>\""
((org-agenda-overriding-header "DUE TODAY")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))
(org-agenda-sorting-strategy '(priority-down))))
(tags-todo "DEADLINE<\"<today>\""
((org-agenda-overriding-header "OVERDUE")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))
(org-agenda-sorting-strategy '(priority-down))))
(agenda ""
((org-agenda-entry-types '(:scheduled))
(org-agenda-overriding-header "SCHEDULED")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'todo 'done))
(org-agenda-sorting-strategy
'(priority-down time-down))
(org-agenda-span 'day)
(org-agenda-start-on-weekday nil)))
(todo "DONE"
((org-agenda-overriding-header "COMPLETED"))))
((org-agenda-format-date "")
(org-agenda-start-with-clockreport-mode nil))) t))
The hotlist keeps a focus on tasks that may be heating up. This has three sections: overdue tasks, ones due within a week, and anything marked FLAGGED.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("fh" "Hotlist"
((tags-todo "DEADLINE<\"<+0d>\""
((org-agenda-overriding-header "OVERDUE")))
(tags-todo "DEADLINE>=\"<+0d>\"+DEADLINE<=\"<+1w>\""
((org-agenda-overriding-header "DUE IN NEXT 7 DAYS")))
(tags-todo "DEADLINE=\"\"+FLAGGED|DEADLINE>\"<+1w>\"+FLAGGED"
((org-agenda-overriding-header "FLAGGED"))))
((org-agenda-todo-ignore-scheduled 'future)
(org-agenda-sorting-strategy '(deadline-up)))) t))
Finally the Hot & Fast view assembles tasks into three sections: overdue, take less then 1 hour, or a score of 1.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("ff" "Hot & Fast"
((tags-todo "DEADLINE<\"<+0d>\""
((org-agenda-overriding-header "OVERDUE")))
(tags-todo "Effort={.}+Effort<\"1:00\""
((org-agenda-overriding-header "QUICK")))
(tags-todo "SCORE=1"
((org-agenda-overriding-header "EASY"))))) t))
After processing incoming items and focusing on some work, there is the step of reviewing to ensure that progress is being made in the right spots.
The set of views start on the r
key.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("r" . "REVIEW...") t))
On a weekly basis, the view (rw
) prvides a look back and look
forward for all tasks.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("rw" "Weekly review"
((tags "{INBOX}&LEVEL<=2"
((org-agenda-overriding-header "NEW TASKS")))
(agenda ""
((org-agenda-clockreport-mode t)
(org-agenda-format-date
(concat "\n"
"%Y-%m-%d" " %a "
(make-string (window-width) ?_)))
(org-agenda-overriding-header "PAST WEEK")
(org-agenda-prefix-format " %?-11t %i %-12:c% s")
(org-agenda-show-log 'clockcheck)
(org-agenda-span 7)
(org-deadline-warning-days 0)))
(agenda ""
((org-agenda-overriding-header "NEXT MONTH")
(org-agenda-span 'month)
(org-agenda-start-day "+0d")
(org-deadline-warning-days 0)))
(todo "PROJECT"
((org-agenda-overriding-header "PROJECT LIST")))
(todo "DONE|PROJECTDONE"
((org-agenda-overriding-header
"Candidates to be archived"))))) t))
The first review section is for all tasks and launchable via
ra
.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("ra" . "All Tasks...") t))
The first view lists tasks grouped by due dates. The sections of this view list tasks that are overdue, due today, due tomorrow, due within a week, due within a month, due later, waiting for, and tasks without a due date.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("rad" "All Tasks (grouped by Due Date)"
((tags-todo "DEADLINE<\"<+0d>\""
((org-agenda-overriding-header "OVERDUE")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))))
(tags-todo "DEADLINE=\"<+0d>\""
((org-agenda-overriding-header "DUE TODAY")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))))
(tags-todo "DEADLINE=\"<+1d>\""
((org-agenda-overriding-header "DUE TOMORROW")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))))
(tags-todo "DEADLINE>\"<+1d>\"+DEADLINE<=\"<+7d>\""
((org-agenda-overriding-header "DUE WITHIN A WEEK")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))))
(tags-todo "DEADLINE>\"<+7d>\"+DEADLINE<=\"<+28d>\""
((org-agenda-overriding-header "DUE WITHIN A MONTH")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))))
(tags-todo "DEADLINE>\"<+28d>\""
((org-agenda-overriding-header "DUE LATER")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))))
(tags-todo "TODO={WAIT}"
((org-agenda-overriding-header "WAITING FOR")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'deadline))))
(todo ""
((org-agenda-overriding-header "NO DUE DATE")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'deadline)))))
((org-agenda-sorting-strategy '(priority-down))
(org-agenda-write-buffer-name "All Tasks (grouped by Due Date)"))
"~/Documents/Org/all-tasks-by-due-date.pdf") t))
Hitting ra1
lists all tasks (with a due date) by due date.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("ra1" "All Tasks with a due date" alltodo ""
((org-agenda-overriding-header "All Tasks (sorted by Due Date)")
(org-agenda-skip-function
'(org-agenda-skip-entry-if 'notdeadline))
(org-agenda-sorting-strategy '(deadline-up)))) t))
(defconst bnb/org-completed-date-regexp
(concat " \\("
"CLOSED: \\[%Y-%m-%d"
"\\|"
"- State \"\\(DONE\\)\" * from .* \\[%Y-%m-%d"
"\\|"
"- State .* -> *\"\\(DONE\\)\" * \\[%Y-%m-%d"
"\\) ")
"Matches any completion time stamp.")
(defun current-time-ndays-ago (n)
"Return the current time minus N days."
(time-subtract (current-time) (days-to-time n)))
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
`("rac" "Completed view"
((todo "TODO|DONE"
((org-agenda-overriding-header
(format "YESTERDAY [%s]" (format-time-string "%a %d" (current-time-ndays-ago 1))))
(org-agenda-skip-function
'(org-agenda-skip-entry-if
'notregexp
(format-time-string bnb/org-completed-date-regexp (current-time-ndays-ago 1))))
(org-agenda-sorting-strategy '(priority-down))))
(todo "TODO|DONE"
((org-agenda-overriding-header
(format "2 DAYS AGO [%s]" (format-time-string "%a %d" (current-time-ndays-ago 2))))
(org-agenda-skip-function
'(org-agenda-skip-entry-if
'notregexp
(format-time-string bnb/org-completed-date-regexp (current-time-ndays-ago 2))))
(org-agenda-sorting-strategy '(priority-down))))
(todo "TODO|DONE"
((org-agenda-overriding-header
(format "3 DAYS AGO [%s]" (format-time-string "%a %d" (current-time-ndays-ago 3))))
(org-agenda-skip-function
'(org-agenda-skip-entry-if
'notregexp
(format-time-string bnb/org-completed-date-regexp (current-time-ndays-ago 3))))
(org-agenda-sorting-strategy '(priority-down)))))
((org-agenda-format-date "")
(org-agenda-start-with-clockreport-mode nil)))))
As part of a review, timesheets (reachable at rt
) illustrate
where the time went.
(with-eval-after-load 'org
(add-to-list
'org-agenda-custom-commands
'("rt" . "Timesheet...") t))
The first timesheet is a daily one that includes the logged tasks and a clockreport.
(with-eval-after-load 'org
;; Show what happened today.
(add-to-list
'org-agenda-custom-commands
'("rtd" "Daily Timesheet" agenda ""
((org-agenda-log-mode-items '(clock closed))
(org-agenda-overriding-header "DAY TIMESHEET")
(org-agenda-show-log '(clock closed clockcheck))
(org-agenda-span 'day)
(org-agenda-start-with-clockreport-mode t)
(org-agenda-start-with-log-mode t)
(org-agenda-time-grid nil))) t))
Naturally, the level up from a daily timesheet is a weekly
timesheet (reachable via rtw
).
(with-eval-after-load 'org
;; Show what happened this week.
(add-to-list
'org-agenda-custom-commands
'("rtw" "Weekly Timesheet" agenda ""
(
;; (org-agenda-format-date "")
(org-agenda-overriding-header "WEEKLY TIMESHEET")
(org-agenda-skip-function '(org-agenda-skip-entry-if 'timestamp))
(org-agenda-span 'week)
(org-agenda-log-mode-items '(closed clock))
(org-agenda-show-log '(closed clock))
(org-agenda-start-on-weekday 1)
(org-agenda-start-with-clockreport-mode t)
(org-agenda-start-with-log-mode t)
(org-agenda-time-grid '(weekly)))) t))
THe following sections configue global export settings that make sense for HTML and \LaTeX.
For HTML, I just want to inline the links to images.
(setq org-export-html-inline-images t)
I suppress the postamble with org-html-postamble.
(setq org-html-postamble nil)
I’ll use the fancy HTML5 export by default.
(setq org-html-doctype "html5"
org-html-html5-fancy t)
Striped tables are nice in email, but this is terribly difficult due to cruddy CSS support. Luckily, org-html-table-row-tags saves the day and assigns the right classes to the table rows with a little bit of help. This makes styling correct in CSS-reduced instances.
(with-eval-after-load 'org
(setq org-html-table-row-tags
(cons '(cond (top-row-p "<tr class=\"tr-top\">")
(bottom-row-p "<tr class=\"tr-bottom\">")
(t (if (= (mod row-number 2) 1)
"<tr class=\"tr-odd\">"
"<tr class=\"tr-even\">")))
"</tr>")))
For \LaTeX, I want to convert fragments to images, and use minted for any source blocks. I also want to have XeTeX as the backend.
(setq org-export-latex-listings 'minted
org-export-latex-custom-lang-environments
'((emacs-lisp "common-lispcode"))
org-export-latex-minted-options '()
org-highlight-latex-and-related '(latex script entities)
org-latex-to-pdf-process
'("xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"
"xelatex -shell-escape -interaction nonstopmode -output-directory %o %f"))
Other contributed org-mode exporters require activiation to show
up in the export menus. This exporter stylizes html
in a
beautiful way.
(use-package ox-tufte
:after org-mode
:ensure t)
Useful on its own, but required for the Slack exporter, the GFM exporter transforms the current orgmode buffer into markdown (yuck).
(use-package ox-gfm
:after org-mode
:ensure (ox-gfm :depth nil :version "1.0"))
This exporter is not added to the export dispatcher, but provides functions to copy the text into the clipboard or open a buffer with the markdown.
(use-package ox-slack
:after (ox-gfm org-mode)
:bind
(:map org-mode-map
("C-c e c" . org-slack-export-to-clipboard-as-slack)
("C-c e b" . org-slack-export-as-slack))
:ensure t)
I have found clocking to be useful in understanding where my time goes. And Org makes this easy, fast, and painless to do.
The clock has some general settings around persistence (resuming clocks), history length and resuming a task after clocking in twice (interrupted task).
(with-after-elpaca-init
(org-clock-persistence-insinuate)
(setq org-clock-history-length 28
org-clock-in-resume t))
Behavior of the clock can change to accommodate other needs. I like having clocks log into a specific drawer. Also, it is nice to remove zero-time clocks and clock out automatically when an item completes.
(setq org-clock-into-drawer "CLOCK"
org-clock-out-remove-zero-time-clocks t
org-clock-out-when-done t)
Two settings help resolve most clock issues that I have seen. Persisting the clock across sessions helps prevent loss of time by accident. Auto-resolution of open clocks help prompt how to handle the situation where a dangling clock exists.
(setq org-clock-persist 'history
org-clock-auto-clock-resolution 'when-no-clock-is-running)
Two final settings regarding clocking configure how I change and view the clocks. I want any clock reports to include the currently clocked task as well. And for clock editing, I change to 15 minute increments.
(setq org-clock-report-include-clocking-task t
org-time-stamp-rounding-minutes '(1 15))
Org-modules allow for specific functionality within org-mode. These modules (plugins) activate by adding them to the list of org-modules.
(with-eval-after-load 'org
(mapc (lambda (x) (add-to-list 'org-modules x t))
'(org-id
org-habit
org-plot
org-protocol
ol-bookmark)))
By enabling the org-id
module, org
will use globally unique
identifiers for entries.
Some tasks repeat, but you still want to log when you have done
it. I use this to help me always do my weekly or yearly
reviews. By including it in org-modules
, habits get activated.
My one setting blow sets a width for the graph in Agenda View.
(setq org-habit-graph-column 50)
To view habits in the agenda (not just on today), try C-u K
.
To ensure that a task is treated as a habit, the STYLE
property
will have to be set to habit
. Read more in the manual.
External applications can have actions in org-mode
through the
use of protocols. A popular usage is accepting bookmarks from
browsers. Read more about it in the manual.
The org linking system is powerful in the way it can connect to many part of Emacs. This module enables links to any stored bookmark.
Babel is some serious magic for Org files. It enables tangled code blocks (this file), notebook-style interactive documents, delarative diagrams, and nicely-formatted examples.
Each code block can have a different language and be edited remotely in a properly mode’d buffer.
This is the list of languages I like to have enabled for use.
(with-after-elpaca-init
(org-babel-do-load-languages
'org-babel-load-languages
'((calc . t)
(C . t)
(ditaa . t)
(dot . t)
(emacs-lisp . t)
(gnuplot . t)
(latex . t)
(maxima . t)
(perl . t)
(plantuml . t)
(python . t)
(ruby . t)
(shell . t)
(sqlite . t)
(sql . t)
(R . t))))
These two packages assist with making HTTP requets. They can act like an alternative to Postman.
(use-package ob-http :after org-mode
:ensure t)
(use-package ob-restclient
:after org-mode
:ensure (ob-restclient :host github :repo "alf/ob-restclient.el")
:config
(org-babel-do-load-languages
'org-babel-load-languages
(append org-babel-load-languages '((restclient . t)))))
Python needs to find the right executable, so I hard code what babel should use for a command.
(setq org-babel-python-command "python3")
This section holds important small preferences for Org.
If the org files are under DVCS like git, then the edits may happen while open in emacs.
This is a global setting, but most useful for the org files that exists elsewhere.
(use-package autorevert
:ensure nil
:defer 10
:diminish " "
:custom
(global-auto-revert-mode t))
I like to save early and often. In earlier versions of orgmode, I sometimes had the capture buffer/timer crash on me. So, now I save at the top of every hour to be sure.
(with-eval-after-load 'org
(run-at-time "00:59" 3600 'org-save-all-org-buffers))
The default columns are as follows. In English, it shows the item (or task), the score, the effort estimate, and the current clock sum (time already spent).
(setq
org-columns-default-format
"%80ITEM(Task) %5Score{+} %10Effort(Effort){:} %10CLOCKSUM")
There are a collection of settings that define how the headlines, subtrees, and notes render.
For the headline stars, there are two settings of note. I am explicit that I do not want only odd levels. I also like to hide the leading stars.
(setq org-odd-levels-only nil
org-hide-leading-stars nil)
Cycling the headline states can produce different views of the
files. I like this to be as compact as possible, so I try to
squash the lines between the collapsed trees. There is also a
flag to open a file collapsed. This I like too – I get a
compact view of the file and can jump to a relevant section with
C-c C-j
.
(setq org-cycle-separator-lines 0
org-startup-folded 'content)
When using SRC-blocks, org can provide highlighting native to the SRC type. Note that this may slow down some files.
(setq org-src-fontify-natively t)
This list lets org know how to handle the links of given file
types. Most things open inside emacs
, but the others set to
default rely on the OS to supply a program.
(setq org-file-apps
'((auto-mode . emacs)
("\\.x?html?\\'" . default)
("\\.pdf\\'" . default)
("\\.mm\\'" . default)))
By using C-c C-j
, you can jump easily around a large orgfile
such as this one. Naturally, the interface you use to do so is
customizable.
I explicitly set it to the default because I sometimes go back
and forth with the default and outline-path-completion
setting.
(setq org-goto-interface 'outline-path)
IDO integrates well into orgmode. Anytime completion is necessary, I like to use the IDO mechanics.
The outline-path-completion
may conflict with IDO, so then it is
best to have it not use IDO in this case.
(setq org-completion-use-ido t
org-outline-path-complete-in-steps nil)
I define when org should leave a blank line before an item. In my case it is headings and plain list items.
(setq org-blank-before-new-entry
'((heading)
(plain-list-item)))
Also, when inserting a new heading, do so after the current subtree.
(setq org-insert-heading-respect-content t)
Set the indentation to the outline node level.
(setq org-adapt-indentation t)
Setup the path for orgmode to find the jar needed.
(setq org-plantuml-jar-path "/usr/local/Cellar/plantuml/1.2017.18/libexec/plantuml.jar")
Here I add my global properties. The appendix _ALL
indicates to
the system that these are the values avaialble for completion
for each of these properties.
(setq org-global-properties
'(("STYLE_ALL" . "habit")
("Effort_ALL" . "0:10 0:30 1:00 2:00 3:00 4:00")
("Score_ALL" . "1 2 3 5 8")))
Org has a different idea of some of the default emacs commands to make it easier to work with the structures involved.
For C-a
or C-e
within a headline, it will only try to navigate
the headline text the first time. Additional keypresses will
move to the true beginning/ending of lines.
C-k
also can behave specially in headlines depending on its
location. When point is at the beginning, it will kill the
headline and the folded subtree below. In the middle of a
headline, it kills the headline text up to the tags. After the
headline text, it kills the tags.
(setq org-special-ctrl-a/e t
org-special-ctrl-k t)
This collection of settings enhances the visual appeal when working in org-mode.
First, some initial built-in settings to make.This block styles the headlines to hide the stars, and fontify them. The next setting hides emphasis markers on words. And finally, entities are drawn with UTF-8 characters.
(setq org-hide-leading-stars t
org-fontify-done-headline t
org-hide-emphasis-markers t
org-pretty-entities t)
Using Prettify, I add these symbols to make the structure of org-mode files cleaner to read. In the list, the strings on the left are replaced by the elements on the right. Moving a cursor over the symbols will show them in their original form.
(bnb/prettify
"org-mode"
'(("#+BEGIN_SRC" . "⌈")
("#+END_SRC" . "⌊")
("#+begin_src" . "⌈")
("#+end_src" . "⌊")
(">=" . "≥")
("=>" . "⇨")))
Bullet shapes do a better job conveying depth then just position/depth of stars. This snippet loads the package and sets the list of bullets to use.
(use-package org-bullets
:ensure t
:custom
(org-bullets-bullet-list '("◉" "◊" "○" "⧫" "✸" "⬨" "⬟" "⬧" "⬢" "⬫" "⌑" "⬪" "▱"))
(org-ellipsis "˯") ;; Options: ˯⇂↯⤵🠻🢗
:hook (org-mode . org-bullets-mode))
As much as I find this to be ridiculous, interacting with AI via Emacs is likely the one true way. Org-ai provides a natrual interface for real humans.
(use-package org-ai
:ensure t
:commands (org-ai-mode
org-ai-global-mode)
:init
(org-ai-global-mode) ; installs global keybindings on C-c M-a
:custom
(org-ai-default-chat-model "gpt-3.5-turbo")
:config
(org-ai-install-yasnippets))
This adds the ability to have #+begin/end_ai
blocks to have a
“conversation” with ChatGPT or DALL⋅E. Be sure to set
org-ai-openai-api-token to your API token before using.
Make it easy to setup a TODO.org
from within a repo. I use this
to capture thoughts while coding.
(use-package org-repo-todo
:ensure t
:bind (("s-;" . ort/capture-todo)
("s-'" . ort/capture-checkitem)
("s-`" . ort/goto-todos)))
Setups the Org-style interface with Biblio.
(use-package org-ref
:defer 10
:config
(setq org-ref-notes-directory "~/Documents/Personal/Org/Biblio/"
org-ref-bibliography-notes "~/Documents/Personal/Org/Biblio/index.org"
org-ref-default-bibliography '("~/Documents/Personal/Org/Biblio/index.bib")
org-ref-pdf-directory "~/Documents/Personal/Org/Biblio/lib"))
Taking notes in a PDF is a useful trick. org-noter
lets me do
just that.
(use-package org-noter
:ensure t
:bind ("H-n" . org-noter))
The best of all worlds? Org-mode and a Zettelkasten system? Yes, Org-roam sets up a knowledge capture and organization system built on the principles of Zettelkasten.
(use-package org-roam
:ensure t
:after org
:delight " 𝕫"
:hook
(after-init . org-roam-setup)
:init
(setq org-roam-v2-ack t)
:custom
(org-roam-directory (file-truename "~/Documents/Org/zettel/"))
:bind
(("C-c n f" . org-roam-node-find)
("C-c n r" . org-roam-node-random)
("C-c n g" . org-roam-graph)
("C-c n c" . org-roam-capture)
("C-c n j" . org-roam-dailies-capture-today)
(:map org-mode-map
(("C-c n l" . org-roam-buffer-toggle)
("C-c n a" . org-roam-alias-add)
("C-c n i" . org-roam-node-insert)
("C-c n I" . org-roam-insert-immediate)))
(:map org-roam-mode-map
(("C-c n l" . org-roam)
("C-c n t" . org-roam-dailies-find-today)
("C-c n w" . org-roam-dailies-find-tomorrow)
("C-c n d" . org-roam-date)
("C-c n f" . org-roam-find-file)
("C-c n g" . org-roam-show-graph)))))
The Org Query Language helps walk through a builder interface to find, filter, and sort org entries.
(use-package org-ql
:after org
:commands (org-ql-search org-ql-view-sidebar)
:bind
("C-c q s" . org-ql-search)
("C-c q b" . org-ql-view-sidebar)
("C-c q v" . org-ql-view)
("C-c q t" . org-ql-sparse-tree)
("C-c q r" . org-ql-view-recent-items)
(:map org-mode-map
("C-c q f" . org-ql-find))
:ensure t
:demand t)
This package is built on a library of searching functions, so it also has some neat tricks like easily specifying agenda views and building code blocks.
For example, to get a link to a dynamic list of pending items (in this file), click on this: org-ql-search:tags:PENDING.
This replaces the built-in DocView for PDF files. Find out the details on the wiki.
The part that I like over DocView is the ability to add
annotations. They all live on C-c C-a
.
Key | Action |
---|---|
C-c C-a D | delete annotation |
C-c C-a a | attachment dired |
C-c C-a h | add highlight |
C-c C-a l | list annotations |
C-c C-a m | add markup |
C-c C-a o | add strikeout annotation |
C-c C-a s | add squiggly annotation |
C-c C-a t | add text annotation |
C-c C-a u | add underline annotation |
(defcustom bnb/homebrew-prefix ""
"Prefix to use for an alternative path to homebrew items"
:type 'string
:group 'bnb)
(setenv "PKG_CONFIG_PATH"
(let ((hbp bnb/homebrew-prefix))
(setq pdf-info-epdfinfo-program (concat hbp "/usr/local/bin/epdfinfo"))
(mapconcat
'identity
(list (concat hbp "/usr/local/Cellar/libffi/3.2.1/lib/pkgconfig")
(concat hbp "/usr/local/Cellar/zlib/1.2.8/lib/pkgconfig")
(concat hbp "/usr/local/lib/pkgconfig")
"/opt/X11/lib/pkgconfig")
":")))
(use-package pdf-tools
:defer t
:ensure t
:config
(custom-set-variables '(pdf-tools-handle-upgrades nil))
(setq-default pdf-view-display-size 'fit-page)
(pdf-loader-install))
(use-package typst-ts-mode
:ensure (:type git :host sourcehut :repo "meow_king/typst-ts-mode" :files (:defaults "*.el"))
:custom
;; (optional) If you want to ensure your typst tree sitter grammar version is greater than the minimum requirement
;; Note this only check and compare file modification time
(typst-ts-mode-grammar-location (expand-file-name "tree-sitter/libtree-sitter-typst.so" user-emacs-directory)))
(use-package poly-markdown
:ensure t)
This sections holds configuration details for some of the uncategorized major modes.
Working in computer land, I add these additional units to calc
.
(use-package calc
:ensure nil
:commands (calc)
:init
(setq math-additional-units
'((GiB "1024 * MiB" "Giga Byte")
(MiB "1024 * KiB" "Mega Byte")
(KiB "1024 * B" "Kilo Byte")
(B nil "Byte")
(Gib "1024 * Mib" "Giga bit")
(Mib "1024 * Kib" "Mega bit")
(Kib "1024 * b" "Kilo bit")
(b nil "bit")
)))
Instead of the normal “splash” screen, show a dashboard instead.
(use-package dashboard
:unless (daemonp)
:ensure t
:hook
(elpaca-after-init . #'dashboard-insert-startupify-lists)
(elpaca-after-init . #'dashboard-initialize)
:custom
(dashboard-set-file-icons t)
(dashboard-icon-type 'nerd-icons)
(dashboard-set-heading-icons t)
(dashboard-projects-backend 'project-el)
(dashboard-items '((recents . 5)
(agenda . 10)
(projects . 5)
(bookmarks . 10)
(registers . 10)))
:config
(dashboard-modify-heading-icons
'((recents . "nf-oct-pin") ;;
(agenda . "nf-oct-goal") ;;
(projects . "nf-oct-project") ;;
(bookmarks . "nf-oct-bookmark") ;;
(registers . "nf-oct-rows"))) ;;
(dashboard-setup-startup-hook))
GNU Hyperbole enriches buffers with links like hypertext. Try C-h
h
for the binding. And M-RET
for links (explicit and implicit).
(use-package hyperbole
:ensure t
:config (hyperbole-mode 1))
Hardly needing an introduction, this section holds the modes and settings related to authoring in, and connecting to my favorite (and necessary) programming languages and interpreters.
I don’t need much extra for C++ support, but the following snippet makes compilation nicer.
I forget where I snarfed this from, but it does a great job fixing the ANSI escape sequences in compilation buffers.
(use-package ansi-color
:ensure nil
:hook ((compliation-filter . colorize-compilation-buffer))
:config
(defun colorize-compilation-buffer ()
(toggle-read-only)
(ansi-color-apply-on-region compilation-filter-start (point))
(toggle-read-only)))
When modified emacs-lisp
, it is most helpful to use paredit,
paxedit, and eldoc.
(use-package elisp-mode
:ensure nil
:after (major-mode-hydra)
:mode-hydra
(emacs-lisp-mode
(:title "Elisp Commands")
("Eval"
(("b" eval-buffer "buffer")
("e" eval-defun "defun")
("r" eval-region "region")
("d" edebug-defun "edebug-defun"))
"REPL"
(("I" ielm "ielm"))
"Test"
(("t" ert "prompt")
("T" (ert t) "all")
("F" (ert :failed) "failed"))
"Doc"
(("." describe-foo-at-point "thing-at-pt")
("f" describe-function "function")
("v" descrive-variable "variable")
("i" info-lookup-symbol "info lookup"))))
:hook
(emacs-lisp-mode . paredit-mode)
(emacs-lisp-mode . paxedit-mode)
(emacs-lisp-mode . turn-on-eldoc-mode))
When programming in elisp
, a cheat sheet of functions can be very
useful. New to Emacs 28, shortdoc provides that useful, quick,
navigatable list of documentation.
For javascript, the only global setting is to shorten the indentation level to 2.
(setq js-indent-level 2)
Not a programming language, but certainly a language. I typically use this in Org document blocks to generate diagrams. The reference guide is available online.
(use-package plantuml-mode
:ensure t)
(with-after-elpaca-init
(bnb/prettify
"python-base-mode"
'(;; Syntax
("def" . ?ℱ)
("not" . ?❗)
("in" . ?∈)
("not in" . ?∉)
("return" . ?⟼)
("yield" . ?⟻)
("for" . ?∀)
;; Base Types
("int" . ?ℤ)
("float" . ?ℝ)
("str" . ?𝕊)
("True" . ?𝕋)
("False" . ?𝔽)
;; Mypy
("Dict" . ?𝔇)
("List" . ?ℒ)
("Tuple" . ?⨂)
("Set" . ?Ω)
("Iterable" . ?𝔊)
("Any" . ?❔)
("Union" . ?∪)))
(add-to-list
'major-mode-remap-alist
'(python-mode . python-ts-mode)))
The Python Executable Tracker seeks to solve the issue of LSP and python virtual environments.
(defun bnb/pet-setup ()
(setq-local python-shell-interpreter (pet-executable-find "python")
python-sehll-virtualenv-root (pet-virtualenv-root))
(pet-eglot-setup)
(eglot-ensure)
(pet-flycheck-setup)
(flycheck-mode))
(use-package pet
:hook
(python-base-mode . bnb/pet-setup)
(python-base-mode . pet-mode))
(with-eval-after-load 'flycheck
(flycheck-define-checker python-ruff
"A Python syntax and style checker using the ruff.
To override the path to the ruff executable, set
`flycheck-python-ruff-executable'.
See URL `https://beta.ruff.rs/docs/'."
:command ("ruff"
"check"
(config-file "--config" flycheck-python-ruff-config)
"--output-format=text"
"--stdin-filename" source-original
"-")
:standard-input t
:error-filter (lambda (errors)
(let ((errors (flycheck-sanitize-errors errors)))
(seq-map #'flycheck-flake8-fix-error-level errors)))
:error-patterns
((warning line-start
(file-name) ":" line ":" (optional column ":") " "
(id (one-or-more (any alpha)) (one-or-more digit)) " "
(message (one-or-more not-newline))
line-end))
:modes (python-mode python-ts-mode)
:next-checkers ((warning . python-mypy)))
(defun bnb/python-flycheck-setup ()
(progn
(add-to-list 'flycheck-checkers 'python-ruff)
(flycheck-select-checker 'python-ruff)
(flycheck-add-next-checker 'python-ruff 'python-mypy)))
(add-hook 'python-mode-local-vars-hook #'bnb/python-flycheck-setup 'append))
(use-package rust-mode
:ensure t
:custom
(rust-mode-treesitter-derive t)
:config
(add-to-list 'eglot-server-programs
'((rust-ts-mode rust-mode) .
("rust-analyzer" :initializationOptions (:check (:command "clippy"))))))
(use-package flycheck-rust
:after rust-mode
:hook
(flycheck-mode-hook . flycheck-rust-setup))
(use-package toml-mode
:ensure (toml-mode :host github :repo "dryman/toml-mode.el")
:mode "\\.toml\\'")
I also snuck in TOML support for the Cargo.toml files.
For my data crunching tasks, I use R and ESS (emacs speaks statistics) provides an interface (repl) for developing analysis and charts.
(use-package ess
:ensure t
:commands (R)
:custom
(ess-default-style 'Rstudio-)
:init (require 'ess-site))
These utilities make the programming experience easier across many languages.
Similar to spellchecking, Flycheck performs on-the-fly syntax
checking. I don’t have it globally on, but easily available
through H-!
.
(use-package flycheck
:ensure t
;:init (global-flycheck-mode)
:init
(pretty-hydra-define hydra-flycheck (:color amaranth :quit-key "q" :title " FlyCheck")
("Action"
(("c" flycheck-mode "Buffer flycheck")
("g" global-flycheck-mode "Global flycheck mode"))
"Errors"
(("n" flycheck-next-error "Next error")
("p" flycheck-previous-error "Previous error")
("l" flycheck-list-errors "List errors")
("C" flycheck-clear-errors "Clear errors"))
"Explanation"
(("x" flycheck-explain-error-at-point "Explain error")
("h" flycheck-display-error-at-point "Dieplay error")
("s" flycheck-select-checker "Select checker")
("?" flycheck-describe-checker "Describe checker"))))
:bind
("H-!" . hydra-flycheck/body))
To go from wide lines to vertical, prog-fill comes into the rescue.
(use-package prog-fill
:ensure t
:commands (prog-fill)
:hook
(prog-mode . (lambda () (local-set-key (kbd "M-q") #'prog-fill)))
:bind
("H-q" . prog-fill)
:custom
(prog-fill-floating-close-paren-p nil)
(prog-fill-break-method-immediate-p t))
SmartTabs try to do the right thing regarding tabs/spaces in
indentation/alignment. It installs through the package interface. Look for
smart-tabs-mode
.
By default, I’m enabling it in all modes that I can.
Since we are dealing with tabs here, I also take the time to set the default width to 4. Because of the way this mode works, any change in the default width will result in code that still aligns.
(use-package smart-tabs-mode
:defer 10
:ensure t
:init
(setq-default indent-tabs-mode nil)
(smart-tabs-insinuate 'c 'cperl 'c++))
To re-tab the whole file, use C-x h C-M-\
.
Now built into Emacs 29, Tree-Sitter is a new parsing library. Read more about it online. This code sets up the library for Emacs 29 and greater, and installs come common grammars.
(use-package treesit
:ensure nil ;; built-in
:if (version<= "29" emacs-version)
:init
(defun bnb/treesit-setup-install-grammars ()
"Install Tree-sitter grammars if they are absent."
(interactive)
(dolist (grammar
'((css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.20.0"))
(html . ("https://github.com/tree-sitter/tree-sitter-html" "v0.20.1"))
(javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.20.1" "src"))
(json . ("https://github.com/tree-sitter/tree-sitter-json" "v0.20.2"))
(python . ("https://github.com/tree-sitter/tree-sitter-python" "v0.20.4"))
(toml . ("https://github.com/tree-sitter/tree-sitter-toml" "v0.5.1"))
(rust . ("https://github.com/tree-sitter/tree-sitter-rust" "v0.21.2" "src"))
(yaml . ("https://github.com/ikatyang/tree-sitter-yaml" "v0.5.0"))
(typst . ("https://github.com/uben0/tree-sitter-typst" "v0.11.0"))))
(add-to-list 'treesit-language-source-alist grammar)
;; Only install `grammar' if we don't already have it
;; installed. However, if you want to *update* a grammar then
;; this obviously prevents that from happening.
(unless (treesit-language-available-p (car grammar))
(treesit-install-language-grammar (car grammar)))))
;; Optional, but recommended. Tree-sitter enabled major modes are
;; distinct from their ordinary counterparts.
;;
;; You can remap major modes with `major-mode-remap-alist'. Note
;; that this does *not* extend to hooks! Make sure you migrate them
;; also
(dolist (mapping
'((python-mode . python-ts-mode)
(css-mode . css-ts-mode)
(js2-mode . js-ts-mode)
(bash-mode . bash-ts-mode)
(css-mode . css-ts-mode)
(json-mode . json-ts-mode)
(js-json-mode . json-ts-mode)))
(add-to-list 'major-mode-remap-alist mapping))
:config
(bnb/treesit-setup-install-grammars))
Enable structured editing and movement with Combobulate.
(use-package combobulate
:after treesit
:if (version<= "29" emacs-version)
:ensure (combobulate :host github :repo "mickeynp/combobulate")
:custom
;; You can customize Combobulate's key prefix here.
;; Note that you may have to restart Emacs for this to take effect!
(combobulate-key-prefix "C-c o")
;; Optional, but recommended.
;;
;; You can manually enable Combobulate with `M-x
;; combobulate-mode'.
:hook
((python-ts-mode . combobulate-mode)
(js-ts-mode . combobulate-mode)
(html-ts-mode . combobulate-mode)
(css-ts-mode . combobulate-mode)
(yaml-ts-mode . combobulate-mode)
(json-ts-mode . combobulate-mode)))
For all of the webish-stuff, this mode works well. Let’s enable it on the right things.
(use-package web-mode
:defer
:mode "\\.html\\'"
:ensure t
:bind ("H-b" . browse-url-of-file)
:custom
(web-mode-markup-indent-offset 2)
(web-mode-css-indent-offset 2)
(web-mode-code-indent-offset 2)
(web-mode-engines-alist '(("handlebars" . "\\.hbs\\'")))
:config
(add-to-list 'auto-mode-alist '("\\.[agj]sp\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.erb\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.hbs\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.mustache\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.djhtml\\'" . web-mode))
(add-to-list 'auto-mode-alist '("\\.svelte\\'" . web-mode)))
There are two useful shells in emacs: eshell
and ansi-term
.
These settings close ansi-term
when I exit the shell. They also
default to just launching zsh
instead of asking me
(preferred). Then it closes by setting up a hook to ensure some
nice functionality in the terminal mode window.
(defun bnb/term-mode-hook ()
"Setup `term-mode`."
(goto-address-mode)
(setq-local term-buffer-maximum-size 10000))
(use-package term
:ensure nil
:hook (term-mode . bnb/term-mode-hook)
:init
(defalias 'zsh 'ansi-term)
:custom
(ansi-term-color-vector [term term-color-black term-color-red term-color-green term-color-yellow term-color-blue term-color-magenta term-color-cyan term-color-white])
(ansi-color-faces-vector [default bold shadow italic underline bold bold-italic bold]))
My hook above makes it easy to click on links in any output and protects the buffer from runaway printing.
Built-in Eshell can provide a shell that works the same on windows or GNU/Linux. One of the really cool features is that you can define commands to use (like aliases) within the shell and have them directly integrate with emacs.
Turn off any $PAGER
settings inherited in the
environment. Because this is running in Emacs, there is no need
for a pager.
(setenv "PAGER" "cat")
(defun bnb/setup-eshell ()
"Setup functions to run in the eshell hook"
(eshell/alias "em" "emacs")
(eshell/alias "ll" "ls -Aloh")
(pcase system-type
('darwin (eshell/alias "llc" "*ls -AlohG"))
(- (eshell/alias "llc" "*ls -AlohG --color=always"))))
(use-package eshell
:ensure nil
:commands eshell
:hook (eshell-mode . bnb/setup-eshell))
Like many shells, eshell
allows for the definition of custom
commands. The following settings add extra functionality for
command-line use.
Fast fingers are used to typing emacs
at a prompt to open a file.
This gives the same behavior in eshell
.
(defun eshell/emacs (&rest args)
"Open a file in emacs the natural way"
(if (null args)
;; If emacs is called by itself, then just go to emacs directly
(bury-buffer)
;; If opening multiple files with a directory name, e.g.
;; > emacs bar/bar.txt foo.txt
;; then the names must be expanded to complete file paths.
;; Otherwise, find-file will look in the current directory which
;; would fail for 'foo.txt' in the example above.
(mapc #'find-file (mapcar #'expand-file-name (eshell-flatten-list (reverse args))))))
One can also keep the shell active and open files in the other window. (Mnemonic here is emacs other window)
(defun eshell/emo (&rest args)
(mapc
(lambda (f)
(save-selected-window
(find-file-other-window f)))
(mapcar #'expand-file-name (eshell-flatten-list (reverse args)))))
On a windows box, setup grep to be a cygwin version.
(when (eq system-type 'windows-nt)
(with-eval-after-load "eshell"
(defun eshell/grep (&rest args)
(eshell-grep "c:/cygwin/bin/grep.exe" args t))))
For Magit, there are some niceties to add.
(defun eshell/gst (&rest args)
(magit-status-setup-buffer (pop args))
(eshell/echo))
(defun eshell/gd (&rest args)
(magit-diff-unstaged)
(eshell/echo))
(defun eshell/gds (&rest args)
(magit-diff-staged)
(eshell/echo))
See the complete guide to mastering Eshell for more on this. Basically, the cursor stays on the command for editing if necessary.
(with-after-elpaca-init
(with-eval-after-load 'eshell
(require 'eshell)
(require 'em-smart)
(setq eshell-where-to-jump 'begin
eshell-review-quick-commands nil
eshell-smart-space-goes-to-end t)
(add-hook 'eshell-mode-hook 'eshell-smart-initialize)))
This section contains settings and packages relevant to visual elements. The theming of emacs is in the style section.
The set of nerd icons depends on an installed compatible font, but there are many alternatives. Because it is font-based, this will work graphically and in the terminal.
Be sure to run nerd-icons-install-fonts for this to work well.
(use-package nerd-icons
:ensure t)
This package adds icons to th Corfu completion list.
(use-package nerd-icons-corfu
:after corfu
:ensure t
:config
(add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))
Setup dired to use icons next to the file names.
(use-package nerd-icons-dired
:ensure t
:hook
(dired-mode . nerd-icons-dired-mode))
(use-package nerd-icons-ibuffer
:ensure t
:custom
(nerd-icons-ibuffer-human-readable-size t)
:hook (ibuffer-mode . nerd-icons-ibuffer-mode))
Use icons in the completion systems.
(use-package nerd-icons-completion
:ensure t
:after marginalia
:hook
(marginalia-mode . nerd-icons-completion-marginalia-setup)
:init
(nerd-icons-completion-mode))
The following sections describe items that affect the visual elements of Emacs.
These following items make Emacs really beautiful on every platform. I remove the menu bar, tool bar and the scroll bar for starters.
;; Prevent the glimpse of un-styled Emacs by disabling these UI elements early.
(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)
In the fringe area, I like to have markers to show me where the buffer begins/ends on the right. On the left, I have emacs show little dashes where empty lines exist.
In the title bar, I have it print the buffer name, full file name and size.
(setq-default indicate-buffer-boundaries 'right)
(setq-default indicate-empty-lines t)
(setq-default frame-title-format '("%b %f %I"))
By default, I usually want to have vertial window splits. The following settings achieve that.
(setq split-width-threshold 0
split-height-threshold nil)
Emacs faces are text styled with attributes including font, slant, weight, color, etc. These sections go over the necessary settings to make them exactly right in all circumstances.
For configuration and debugging, list-faces-display is useful.
(with-after-elpaca-init
(when window-system
(defun bnb/filter-existing-fonts (fl)
"Filter the list for only existing fonts"
(seq-filter #'font-info fl))
(setq bnb/fontlist
'("-*-LunarISO Nerd Font-regular-normal-condensed-*-*-*-*-*-p-0-iso10646-1"
"LunarISO Nerd Font-12"
"Fira Code-12"
"Fira Code-13"
"Source Code Pro-13"
"Menlo-12"
"Consolas-12")
bnb/font-ring
(ring-convert-sequence-to-ring (seq-filter #'font-info bnb/fontlist))
bnb/font
(ring-ref bnb/font-ring 0))
(defun bnb/font-apply (font)
"Change the default font to FONT."
(set-frame-font (setq bnb/font font))
(message "Set default font to %s" bnb/font))
(defun bnb/font-next ()
"Cycle the default font to the next in the ring."
(interactive)
(bnb/font-apply (ring-next bnb/font-ring bnb/font)))
(defun bnb/font-prev ()
"Cycle the default font to the previous in the ring."
(interactive)
(bnb/font-apply (ring-prev bnb/font-ring bnb/font)))
(set-frame-font bnb/font)
(bind-keys
("H-f" . bnb/font-next)
("H-F" . bnb/font-prev))))
Support unicode fonts throughout the system. Be sure to install recommended fonts for support.
(use-package unicode-fonts
:disabled t
:ensure t
:defer 10
:init
(unicode-fonts-setup))
On Windows, this function shows a font selection screen and sets the default face. This will not persist between sessions, but is great for test driving.
(defun bnb/windows-set-font ()
"Use Windows font selection to set the default font."
(interactive)
(set-face-attribute 'default nil :font (w32-select-font)))
Changing font sizes in presentations is crucial to have at hand. I
use the following keybindings. C--
overrides the negative argument
function, but that one is also accessible by M--
.
(defun bnb/change-frame-font-size (fn)
"Change the frame font size according to function FN."
(let* ((font-name (frame-parameter nil 'font))
(decomposed-font-name (x-decompose-font-name font-name))
(font-size (string-to-number (aref decomposed-font-name 5))))
(aset decomposed-font-name 5 (int-to-string (funcall fn font-size)))
(set-frame-font (x-compose-font-name decomposed-font-name))))
(defun bnb/frame-text-scale-increase ()
"Increase the frame font size by 1."
(interactive)
(bnb/change-frame-font-size '1+))
(defun bnb/frame-text-scale-decrease ()
"Decrease the frame font size by 1."
(interactive)
(bnb/change-frame-font-size '1-))
(with-after-elpaca-init
(bind-keys
("C-+" . text-scale-increase)
("C--" . text-scale-decrease)
("s--" . bnb/frame-text-scale-decrease)
("s-+" . bnb/frame-text-scale-increase)
("s-=" . bnb/frame-text-scale-increase)))
I dislike the box around the mode-line
making it look like a
button. I disable (set to nil
) the box
face attribute to get a
flat feel. Be sure to do it to all mode-line
faces that have this
attribute.
(set-face-attribute 'mode-line nil :box nil)
(set-face-attribute 'mode-line-inactive nil :box nil)
(set-face-attribute 'mode-line-highlight nil :box nil)
If I ever use a font with a missing glyph, this will let Emacs check the Symbola font for the missing data.
Download Symbola if you do not have it.
(set-fontset-font "fontset-default" nil
(font-spec :size 20 :name "Symbola"))
Make the cursor the full width of the character at point.
(setq x-stretch-cursor t)
This function ensures that all enabled themes are shut down – not just the most current one.
(defun bnb/disable-all-themes ()
"Disable all enabled themes."
(interactive)
(mapc #'disable-theme custom-enabled-themes))
On creating themes: https://www.gnu.org/software/emacs/manual/html_node/emacs/Creating-Custom-Themes.html#Creating-Custom-Themes
I like to have a few options for themes easily available. This set respresents my favorite go-to combinations.
(use-package minimal-theme :ensure t :defer t)
(use-package gruvbox-theme :ensure t :defer t)
(use-package material-theme :ensure t :defer t)
(use-package tango-plus-theme :ensure t :defer t)
(use-package color-theme-sanityinc-tomorrow :ensure t :defer t)
;; dichromacy
;; adwaita
This is a nice addition to any modeline. In a little block, it shows the date, time, moon phase
(use-package sky-color-clock
:ensure
(sky-color-clock :host github :repo "zk-phi/sky-color-clock")
:commands (sky-color-clock)
:custom
(sky-color-clock-enable-emoji-icon t)
:config
(sky-color-clock-initialize 40)
(sky-color-clock-initialize-openweathermap-client
bnb/openweathermap-api-key
bnb/openweathermap-city-id))
The variables containging my city and API key are stored in local customizations.
Keeping with the goal of a clean and tidy Emacs, Doom modeline provides that fancy, fast, and minimal design.
I only customize the height to hug the font and keep everythig small.
(use-package doom-modeline
:ensure t
:init
(doom-modeline-mode t)
:custom
(doom-modeline-height 0))
Let’s try ⁂-mode for an interesting mini-buffer line. It writes information into the minibuffer. Here I have it set to show the output of sky color clock.
(use-package asterism-mode
:after sky-color-clock
:ensure
(asterism-mode :host gitlab :repo "lunar.studio/asterism-mode")
:init
(defun bnb/smaller-sky-color ()
(let* ((scc (sky-color-clock))
(len (length scc)))
(add-face-text-property 0 len '(:height 90) t scc)
;(add-face-text-property 0 len '(:justification right) t scc)
scc))
:config
(setq ⁂-format '((:eval (bnb/smaller-sky-color)))))
Emacs can be so pretty sometimes. The modes that follow are great for mathematical notation in Emacs.
The built-in prettify-symbols-mode is easy to use. Check out what it does (and how it can be adjusted) in prettify-symbols-alist.
I have additional python settings to make it look sharp.
(setq global-prettify-symbols-mode t
prettify-symbols-unprettify-at-point 'right-edge)
Some resources for additional customization:
- http://endlessparentheses.com/using-prettify-symbols-in-clojure-and-elisp-without-breaking-indentation.html
- https://www.emacswiki.org/emacs/PrettySymbolsForLanguages
Having nice colors can help understand status, types, symbols, or even nesting.
In order to see the colors in the buffer this mode higlights color words and definitions with their values.
(use-package rainbow-mode
:commands (rainbow-mode)
:ensure t)
Color helper KureColor allows easy modifications of hue, saturation and brightness.
(use-package kurecolor
:bind (("H-k" . kurecolor-increase-hue-by-step)
("H-j" . kurecolor-decrease-hue-by-step)
("s-k" . kurecolor-increase-saturation-by-step)
("s-j" . kurecolor-decrease-saturation-by-step)
("s-l" . kurecolor-increase-brightness-by-step)
("s-h" . kurecolor-decrease-brightness-by-step))
:ensure t)
These elements are at the end to keep all customizations tidy.
I typically use the customize interface to generate any local settings such as proxies, paths, fonts, etc. that may vary from machine to machine.
(setq custom-file "~/.emacs.d/custom.el")
(load-file custom-file)
I also intend to have a generic call to an installed local file
that may need to behave differently from custom.el
. This loads
last so that it can modify any existing setting made here to work
on the specific system in question.
In the code below, I add ~/.emacs.d/
to the load path and have a
protected call to load-library
. If the file exists, it gets
loaded, otherwise the error normally returned if the file is
non-existent gets ignored.
(condition-case err
(progn
(load-file (format "~/.emacs.d/%s.el" user-login-name))
(message "Loaded local settings file %s.el" user-login-name))
(file-error
(message "Skipping %s library as it does not exist." user-login-name))
nil)
This message prints when Elpaca loading is complete
(message "README EVALUATION DURATION: %3.2f seconds" (- (float-time) bnb/start-time))
(with-after-elpaca-init
(message "ELPACA LOADING COMPLETE: %s" (emacs-init-time)))
The local variables here setup a save hook (just in this buffer) that will create full.el for loading.
I used to use Org to tangle on the fly during initialization, but this method supports a pre-tangling that speeds up initialization.