Skip to content

bnbeckwith/bnb-emacs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Beckwith Tangled Emacs Initialization

Introduction

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.

Installation

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.

Notes

This section has specific notes that are relevant to my emacs setup in general and this document in particular.

Elpaca

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:

OperationUI (keys apply in elpaca-ui-mode)completing-read interface commands
Finding PackagesM-x elpaca-managerelpaca-try
Trying Packages (for current session)i xelpaca-try
Fetching Package Updatesf xelpaca-fetch or elpaca-fetch-all
Merging Updatesu xelpaca-merge or elpaca-merge-all
Pulling Updates*C-u u xC-u M-x elpaca-merge or C-u M-x elpaca-merge-all
Rebuilding Packagesr xelpaca-rebuild
Deleting Packagesd xelpaca-delete
View Package Logsl filters log to current packageelpaca-log
Visit Package Repository Directoryvelpaca-visit
Visit Package Build DirectoryC-u vC-u M-x elpaca-visit
Browse Package Websitebelpaca-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)))

Emacs Build

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

Pending sections

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.

Performance

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.

Preamble

This sections houses the settings that need to be made up front and support subsuquent package installation and activiation.

Default Setup

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"))

Local customizations (custom.el, username.var.el)

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.

Local variables (user-login-name)

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)

Early Initialization

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.

Init debug assistance

     (defmacro comment (&rest body)
	"Comment out sexps in BODY"
	nil)

Libraries

This section hosts early loading of libraries required by subsequent packages.

Dash

The modern list library, Dash, provides a set of common list manipulation functions (all prepended with ‘-‘, hence the name).

(use-package dash
  :ensure t)

Delight

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)

Hydra

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)

Major mode and Pretty Hydra

(use-package major-mode-hydra
  :ensure t
  :demand t
  :bind  ("s-." . major-mode-hydra))

Seq

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)))

Coda

;;; Wait for this to be processed before packages that depend on it
(elpaca-wait)

Settings

The sections here contain mostly settings that configure keymaps, command launchers, built-in features, and other details for day-to-day life.

Keys

These sections contain setting related to keys and keymaps.

Binding Keys

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)

Personal Keymaps

The following settings are inspired from http://endlessparentheses.com/the-toggle-map-and-wizardry.html.

Toggle Map

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?

KeyFunctionDescription
ccolumn-number-modeToggle column number display in the modeline
etoggle-debug-on-errorEnter debugger on error
utoggle-debug-on-quitEnter debugger on C-g
fauto-fill-modeAutomatic line breaking
ttoggle-truncate-linesTruncate long lines in the buffer
rdired-toggle-read-onlyRead-only mode
wwhitespace-modeWhitespace visualization
borgtbl-modeUse org table minor mode (non-org buffers)
xbnb/transparency-nextCycle forward through transparency settings
Xbnb/transparency-previousCycle backward through transparency settings
Bdisplay-battery-modeShow battery info in modeline
lhl-line-modeHighlight current line
mbnb/hide-mode-line-modeToggle 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))

Whitespace

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 " 🟂")
Deletion

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))
Scroll window up/down

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))))
Align Regexp

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)))

Kill current buffer

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)))

Super keys

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.

Command Launchers

This section holds the settings for my two main command launchers: hydra and vertico.

Hydra

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.

Vertico

Or VERTical Interactive COmpletion, is my preferred completion interface.

(use-package vertico
  :ensure t
  :config (vertico-mode))

Vertico Directory

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))

Embark

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))

Orderless

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.

CharacterEffect
!Does not match following literal
,Matches initial characters
===Forces a literal match
~Uses the flex matching
%Matches while ignoring diacritics

Consult

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.

Marginalia

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)))

Expansion & Completion

This section defines interations with text expansion systems.

Abbrev

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))

Cape

“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)
  )

Corfu

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))

Terminal support

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"))

Yasnippet

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)

Hippie Expand

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))

Built-in Features

Emacs comes with some nice batteries. This section configures my favorites.

Backups

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

Default File encoding

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))

Native Compilation

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))

Path

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)))

Server

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))

Sounds

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))

Recentf

I enable emacs remembering recently open files. For my setup, this feeds into the candidates for Consult.

(with-after-elpaca-init
  (recentf-mode t))

Timezones

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")))

Isearch

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)

Minibuffer

This section holds any minibuffer settings.

Minibuffer History

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))

Movement

Getting around takes a little tweaking. This section holds the details on how movement is defined for me.

Ace Utilities

The Ace (and subsequent Avy) packages aid in jumping the cursor to the right place in the buffer.

Ace Flyspell

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")))))

Ace Isearch

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))

Ace Link

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))

Ace Window

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)))))

Avy Goto

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.

Avy Zap

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))

Errors

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)

Read-only helpers

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)))

Scroll Screen Position

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.

Custom Helpers

This is a collection of code specific to how I use emacs. Some are from different websites or other Emacs users.

Auto-display agenda

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))

Auto-indent when pasting

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))))))

Better window splitting functions

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)))

Hide mode line

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))

Open/Edit This file

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"))))

Org-column resizing

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)

Prettify macro

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"))))))

Styled HTML Export

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 " " "&nbsp;" 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))

Transparency

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))))

Weekly Score Goal in Org-Agenda

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))

Weekly Time Reporting

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)))

Workweeks

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))))

Editing

Similar to movement, editing happens every day, so I use a few customizations to make it nice.

Ediff Window Setup

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.

Multiple Cursors

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")))))

Regexp-Builder

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 BindingMeaning
C-c TABSwitch syntax
C-c C-eSub-expression mode (show matching groups)
C-c C-s/rSearch forward/backward
C-c C-wCopy regexp to kill ring
C-c C-qQuit the builder

Be sure to consult the syntax of regular expressions to learn more about the weird backslashing.

Executable Scripts on save

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)

Auto Reverting

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)

Images

Emacs does a good job with images, so any particular preferences are handled in this section.

ImageMagick

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)))

Minor Modes

The minor modes can be used in a variety of situations to enhance the editing experience overall.

Reference

These modes help present reference material.

Helpful

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))

Which Key

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))

Dictionary

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)))

OSX Dictionary

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))

Editing

This section covers minor modes that personalize and improve the editing experience.

Adaptive Fill

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)))

Focus 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))

Common User Access 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.

CUA Rectangles

These take place with an active rectangle. To start/cancel a rectangle use C-RET.

KeysFunction
M-<arrow>Move rectangle overlay
C-<SPACE>Activate region bounded by rectangle
M-aAlign all words at the left edge
M-bFill rectangle with blanks (tabs and spaces)
M-cCloses the rectangle by removing left edge blanks
M-fFills the rectangle with a single character (prompt)
M-iIncreases number found on each line of rectangle
M-kKills the rectangle as normal multi-line kill
M-lDowncases the rectangle
M-mCopies the rectangle for normal multi-line paste
M-nFills each line with increasing numbers (prompt)
M-oOpens the rect by moving hilighted text right and filling with blanks
M-pToggles virtual straight rectangle edges
M-PInserts tabs and spaces to make real straight edges
M-qPerforms text filling on the rectangle
M-qPerforms text filling on the rectangle
M-rReplaces REGEXP (prompt) by STRING (prompt) in rectangle
M-RReverse the lines in the rectangle
M-sFills each line of the rectangle with the same STRING (prompt)
M-tPerforms text fill of the rectangle with TEXT (prompt)
M-uUpcases 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
CUA Global Mark

The global mark feature enables a target the receives any typed/copied/killed text from any buffer (even the current one).

Keyfunction
<ch>All characters (including newlines) you type are inserted at the global mark!
C-xIf you cut a region or rectangle, it is automatically inserted at the global mark, and the global mark is advanced.
C-cIf you copy a region or rectangle, it is immediately inserted at the global mark, and the global mark is advanced.
C-vCopies a single character to the global mark.
C-dMoves (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-spaceJumps to and cancels the global mark.
C-u S-C-spaceCancels the global mark (stays in current buffer).
<TAB>Indents the current line or rectangle to the column of the global mark.

Easy Kill

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.

KeySaves at point
M-w wword
M-w ssexp
M-w llist
M-w ddefun
M-w Ddefun name
M-w ffilename
M-w bbuffer file name or default directory

There are also modifiers to treat how the saved text is handled.

ModifierEffect
@append to previous kill
C-wkill selection
+, -, 1..9expand/shrink selection
0shrink selection to initial size
<SPC>cycle through easy-kill-alist
C-<SPC>turn selection into active region
C-gabort
?help

Vundo

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.

KeyEffect
fgo forward
bgo backward
ngo to the node below at branch point
pgo to the node above
ago back to last branch
ego to the end of the branch
lgo to last saved node
rgo to next saved node
mmark current node for diff
uunmark marked node
dshow a diff
qquit (C-g also works)

Expand Region

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))

Citar (Bibtex completions)

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))

Tree Sitter

Introduced in emacs 29, tree sitter transforms code into a concrete syntax tree. Read up on how to get started with tree-sitter

Treesit Auto

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))

Version Control

Emacs is fantastic for interfacing with version control systems. For git, it may have the best interface.

Magit

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"))

Release 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.

Forge

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))

Smerge

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")))))

Checking

Authors can always use that little bit of extra help to ensure the prose is right from the beginning.

Writegood Mode

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))

Spell Checking

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)))

Proselint

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))

Buffers

Handling buffers is central to an effective Emacs experience. This section adds in the tools to make management easy.

Midnight Mode

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)

IBuffer

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"))))

Groups

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\*"))))))
VC Grouping

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)))

Unique Buffer Names

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 ":"))

OSX Reveal

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))

Development

The minor modes for development deal mainly with parenthenses and structured editing.

Check parens on save

This check has saved me from a broken configuration file many times. I highly recommend.

(add-hook 'after-save-hook  'check-parens nil t)

Eldoc

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-"))

Eglot

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.

Paredit

I added paredit-mode to several of the lisp modes that follow.

Paredit Cheatsheet

Animated Paredit

(use-package paredit
  :ensure t
  :delight " 🍐"
  :hook
  (emacs-lisp-mode . paxedit-mode)
  (clojure-mode . paxedit-mode)
  :commands (paredit-mode))

Paxedit

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)))

Rainbow Delimiters

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))

Organization

There are a couple of built-in features that help with organization. (Outside of org-mode, that is)

Bookmarks

http://emacswiki.org/emacs/BookMarks

KeystrokeAction
C-x r mSet a bookmark
C-x r bJump to a bookmark
C-x r lList your bookmarks
M-x bookmark-deleteDelete 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)

Project

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

BindingFunction
C-x p pFind project
C-x p fFind file in project
C-x p bFind buffer in project
C-x p eEshell in project

Major Modes

This section holds the details for major modes. These are interfaces in their own right.

File Management

Finding files in the minibuffer is easy with the completion frameworks above, but Emacs also functions very well as a fully fledged file manager.

Dired

The built-in directory editor, dired, can be customized to a great degree. The following sections walk us through my particular preferences.

Basic Settings

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.

Hacks

These are part of the dired-hacks repository.

(use-package dired-hacks-utils
  :defer t
  :ensure t)
Filter mode

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))
Colorized Files

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.*")))
Narrowing

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)
Collapsing directories

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)
External Commands

Also, there is a nice faculty to run an external command on a given file with ==!==.

Neotree

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)))

Documents

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.

AucTeX

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

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)
KeystrokeFunction
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

HTML

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.

Impatient mode

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

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)))

Markdown

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"))

Orgmode

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.

BindingEffect
C-c tEdit tables in any mode
C-c lStore a link to thing at point
C-c rDispatch org capture
<f12>Open org agenda
H-zOpen org agenda
<apps>Open org agenda
<f9> gGoto open org clock
<f9> iOrg clock in
<f9> oOrg clock out

Contrib

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)

Auto mode

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.

Hooks

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))

Speed Keys

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))))))))

Org Configuration

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.

Keywords

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@/!)"))))
Tags

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))))
Mechanics

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))
Logging

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")))
Sub-tasks

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))

Capture

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.

Capture-template helpers for data tables

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)

Refile

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)

Agenda

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 "")
Super agenda

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).

Category Icons

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))))
Views

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)
                                             "      " "┈┈┈┈┈┈┈┈┈┈┈┈┈")))))))))
Process

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))
Focus

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))
Review

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))
All Tasks

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)))))
Timesheets

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))
  

Export

THe following sections configue global export settings that make sense for HTML and \LaTeX.

HTML

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>"))) 
LaTeX

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"))
Tufte

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)
Github Flavored Markdown

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"))
Slack

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)

Clocking

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))

Modules

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)))
Global Ids

By enabling the org-id module, org will use globally unique identifiers for entries.

Habit

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.

Org protocol

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.

Bookmarks

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

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))))
Extra Babel Packages

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)))))
Customization

Python needs to find the right executable, so I hard code what babel should use for a command.

(setq org-babel-python-command "python3")

Miscellaneous Settings

This section holds important small preferences for Org.

Auto-revert mode

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))
Automatically save org files

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))
Columns

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")
Display settings

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)
File Applications

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)))
Goto Interface

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 Integration

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)
Insertion

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)
Plantuml

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")
Properties

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")))
Special Control Keys

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)

Pretty Org-mode

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"   . "")
   (">="          . "")
   ("=>"          . "")))
Org Bullets

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))

Org-AI

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.

Org Repo Todo

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)))

Org Ref

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"))

Org Noter

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))

Org Roam

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)))))

Org QL

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.

PDF Tools

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.

KeyAction
C-c C-a Ddelete annotation
C-c C-a aattachment dired
C-c C-a hadd highlight
C-c C-a llist annotations
C-c C-a madd markup
C-c C-a oadd strikeout annotation
C-c C-a sadd squiggly annotation
C-c C-a tadd text annotation
C-c C-a uadd 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))

Typst

(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)))

Polymode

(use-package poly-markdown
  :ensure t)

Miscellaneous

This sections holds configuration details for some of the uncategorized major modes.

Calc

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")
          )))

Dashboard

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))

Hyperbole

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))

Programming Languages

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.

C++

I don’t need much extra for C++ support, but the following snippet makes compilation nicer.

Compilation Buffers

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)))

Elisp

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.

Javascript

For javascript, the only global setting is to shorten the indentation level to 2.

(setq js-indent-level 2)

Plantuml

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)

Python

(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)))

PET

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))

Ruff

     (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))

Rust

(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.

Emacs Speaks Statistics (ESS)

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))

Utilities

These utilities make the programming experience easier across many languages.

Flycheck

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))

Prog fill

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))

Smart Tabs

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++))
Notes

To re-tab the whole file, use C-x h C-M-\.

Treesit

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))

Combobulate

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)))

Web 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)))

Shells

There are two useful shells in emacs: eshell and ansi-term.

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.

Eshell

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.

Eshell Settings

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))

Eshell Commands

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))

Plan 9 Smart Shells

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)))

Visual

This section contains settings and packages relevant to visual elements. The theming of emacs is in the style section.

Nerd Icons

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)

Icons in Corfu

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))

Icons in dired

Setup dired to use icons next to the file names.

(use-package nerd-icons-dired
  :ensure t
  :hook
  (dired-mode . nerd-icons-dired-mode))

Icons in Ibuffer

Add icons to ibuffer.

(use-package nerd-icons-ibuffer
  :ensure t
  :custom
  (nerd-icons-ibuffer-human-readable-size t)
  :hook (ibuffer-mode . nerd-icons-ibuffer-mode))

Icons in completion

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))

Style

The following sections describe items that affect the visual elements of Emacs.

Frame Changes

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)

Window Changes

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"))

Window Splitting

By default, I usually want to have vertial window splits. The following settings achieve that.

(setq split-width-threshold 0
      split-height-threshold nil)

Faces

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.

Default Fonts

(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))))

Unicode

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))

Trying out fonts on Windows

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)))

Dynamic Font sizes

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)))

Mode Line Style

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)

Missing Glyphs

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"))

Cursor width

Make the cursor the full width of the character at point.

(setq x-stretch-cursor t)

Themes

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

Extra 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

Sky Color Clock

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.

Doom Modeline

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))

Asterism (⁂) Mode

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)))))

Prettify Symbols

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:

Color

Having nice colors can help understand status, types, symbols, or even nesting.

Rainbow Mode

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)

KureColor

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)

Postamble

These elements are at the end to keep all customizations tidy.

Local customizations (custom.el)

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)

Local customizations (user-login-name)

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)

Housekeeping

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.

About

My (tangled) emacs config

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages