Emerging from dotemacs bankruptcy the hard way: getting about
We want to maximize our ability to "stay in The Zone". So the aim is to create the fastest, smoothest, tightly integrated, and unobtrusive mechanism to get things done using the keyboard alone.

My Emacs sessions often look like this. One or more persistent windows of work-in-progress files, and one or more transient windows or buffer contexts for functions like search, magit, documentation lookup, linter feedback so forth. A seamlessly intermeshed set of modes delivers a fluid workflow that I love.

We craft our super slick keyboard-driven1 "getting about" user experience and interface using a composite of:

  • enhanced completions (company, counsel, which key)
  • narrowing (ivy)
  • search (swiper + ivy)
  • remembering state and history (save file/history/desktop etc…)
  • window management (golden ratio mode)
  • navigation methods (key-chord, avy etc.)
  • surfacing meta-information (documentation, argument lists etc.)
  • enhanced project navigation (projectile)
  • etc…

I say composite, because in isolation any one of those enhancements is marginally useful. It is only when the big and little details interplay with each other, that we achieve an experience far greater than the sum of the parts.

But first, why go through all this trouble?

Because professionals like their interfaces organised and dense and fast to use (to access, execute, discover). Think about car dashboards (the good ones), airplane instrumentation, network operations centers, intensive care units and so forth. I too like information-dense heads up display style interfaces, with functionality that adapts to changing context, provided it does so in a predictable, globally consistent, unobtrusive manner.

The picture at the beginning of this post shows what my Emacs session often looks like, typically when I'm deep in The Zone, thinking about something, researching, solving problems, programming, writing etc. In fact, the image shows my dotemacs configuration open (main vertical), with a help window for a function that I wanted to double-check (right-top), and a draft of this very blog post (right-bottom) because I was cross-referencing my code while writing the post.

I also threw in a search for good measure (the temporarily embiggened echo area), which is one of the ways I use to narrow down to bits of code I want to visit. Here it shows search in use to jump between use package definitions. The echo area collapses down to 1 line high (the default), once a search is cancelled or completed (hit RET on a result to go to that place in the buffer being searched).

The layout was auto-rebalanced by golden-ratio-mode. So, if I move point to any other window, golden-ratio-mode gives the newly-focused the most visual space, while keeping the overall view aesthetically pleasing and useful (enough is visible in all windows).

And all commands are either in muscle memory, or a few keystrokes away via context-rich on-demand command-and-control panels powered by relevant modes that mesh together like swiper, ivy, counsel, imenu, company, amx.

The nice videos below elaborate the interaction model I am going for. You will have to imagine the composite of it all, or try it out for yourself!

Video: How the packages Ivy, Counsel, and Swiper work together.

  • Ivy is a "narrowing and completion framework".
  • Swiper uses Ivy to enhance the isearch experience.
  • Counsel surfaces access to useful commands, using an interface / interaction model that composes with Ivy and Swiper functionality.

Video: More neat Swiper search stuff.

Video: Avy lets us navigate visually using treemps. Treemaps are cool!

Video: Perfect automatic window resizing, using golden-ratio-mode.

Years ago I learned of golden-ratio mode from my friend and colleague Adityo, and I've never looked back since. As I mentioned before, I like to keep multiple mutually related contexts visible by opening things in various windows (e.g. edit code, and tests, and view linting at one go). And then pull up version control (magit) when needed without removing existing context. Golden ratio mode makes sure I can do all this without have to manually fiddle with window sizes or layouts. It generally creates all the real estate I need, for the window in use.

Video: imenu lets us pull up overviews of structure of the given context, e.g. when editing a markdown or orgmode file, I can pull up a navigable and searchable table of contents using headings. Likewise, when editing code, I can pull up a navigable and searchable listing of top-level names (vars, constants, functions, macros etc).

Video: Completions via CompAny mode (as in "Complete Anything").

And I use key-chord mode to give me lightning-quick mnemonic access to very frequently used commands. I haven't counted but I think the chords jj (jump to word), jk (jump to char), g; (goto window) must be getting used a hundred times a day to navigate within a text, and across window contexts.

(key-chord-define-global "jj" 'avy-goto-word-1)
(key-chord-define-global "jl" 'avy-goto-line)
(key-chord-define-global "jk" 'avy-goto-char)
(key-chord-define-global "XX" 'execute-extended-command)
(key-chord-define-global "yy" 'counsel-yank-pop)
(key-chord-define-global "g;" 'ace-window)
(key-chord-define-global ";g" 'ace-window)

Along with this, I also have configured other quality of life improvements like newer and improved keybindings, yasnippet, some sundry tweaks etc.

Many details can be fine-tuned, if need arises. For now, this is quite a satisfying setup that I understand fairly well.

The init.el so far (previously, before that, even earlier)…

We get around the place handsomely now
;;; package --- init.el  -*- lexical-binding: t -*- --- My Emacs configuration.

;;; Commentary:

;;; This file is not part of GNU Emacs.

;;; Author: Aditya Athalye
;;; Created on: 30 June 2023
;;; Copyright (c) 2023 Aditya Athalye

;;; License:
;;; This program is free software; you can redistribute it and/or
;;; modify it under the terms of the MIT license, which is included
;;; with this distribution. See the LICENCE.txt file.

;;; Code:

;; Set the low bar Emacs compatibility high
(defvar dotemacs-min-version "28.1")

(when (version< emacs-version dotemacs-min-version)
  (error "We demand spiffy new Emacs, at least v%s, but you have v%s"

;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Take clues from bbatsov/prelude, except keep structure relative to our
;; initial dotemacs-dir path. This way we can start the user's emacs via
;; ~/.emacs.d symlinked to the dotemacs repo, and develop/debug against
;; the repo without potentially overwriting transient state files of the
;; daily driver .emacs.d.

(defvar dotemacs-dir
  (file-name-directory (or load-file-name (buffer-file-name)))
  "The dotemacs' root.  Normally it should be ~/.emacs.d.")

(defvar dotemacs-custom-file (expand-file-name "custom.el" dotemacs-dir)
  "Make Emacs add customisations here, instead of the init file.
Usually customisations made from the UI go into `custom-file'.")
(setq custom-file dotemacs-custom-file)
(unless (file-exists-p dotemacs-custom-file)
  (make-empty-file dotemacs-custom-file))
(load-file custom-file) ; load *now*, instead of unpredictable load sequence

(defvar dotemacs-savefile-dir (file-name-as-directory
                               (expand-file-name "savefile" dotemacs-dir))
  "This folder stores all the automatically generated save/history-files.")
(unless (file-exists-p dotemacs-savefile-dir)
  (make-directory dotemacs-savefile-dir))

;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; via
;; - technomancy/better-defaults
;; - bbatsov/prelude
;; - vedang/emacs-up
;; - suvratapte/dot-emacs-dot-d

;; but keep up conventional appearances of 8 character wide tabs.
;; NOTE: Use `C-q TAB` to insert literal tabs.
(setq-default indent-tabs-mode nil
              tab-width 8
              fill-column 80)

 ;; gc ~100MB for better overall performance. ~50-100MB is recommended
 ;; these days over the long-obsolete default of ~8MB.
 gc-cons-threshold (* 100 1024 1024)
 ;; Large files freeze Emacs. Warn for files over ~100MB.
 large-file-warning-threshold (* 100 1024 1024)
 ;; Always load newest byte code. cf. bbatsov/prelude
 load-prefer-newer t

 inhibit-startup-message t
 ring-bell-function 'ignore ; no beeps
 require-final-newline t ; always well-form files
 confirm-kill-emacs 'y-or-n-p ; instead of disabling 'C-x C-c'
 create-lockfiles nil
 tab-always-indent 'complete
 tab-first-completion 'word
 enable-recursive-minibuffers t
 minibuffer-depth-indicate-mode t
 uniquify-buffer-name-style 'forward
 uniquify-after-kill-buffer-p t ; rename after killing uniquified
 uniquify-ignore-buffers-re "^\\*") ; spare special buffers

;; http://ergoemacs.org/emacs/emacs_save_restore_opened_files.html
(global-auto-revert-mode 1) ; auto-revert buffer if file-on-disk changes
(delete-selection-mode t) ; delete selection for any keypress
(add-hook 'before-save-hook 'delete-trailing-whitespace)
(fset 'yes-or-no-p 'y-or-n-p) ; typing "yes/no" gets annoying fast
(global-unset-key (kbd "C-z")) ; avoid fat fingering `suspend-frame'

;; UTF-8 as default encoding
;; ref: http://ergoemacs.org/emacs/emacs_encoding_decoding_faq.html
(set-language-environment "UTF-8")

;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Go easy on the eyes. This high-contrast darkmode theme is built
;; into Emacs, as of Emacs version 28.1
(load-theme 'modus-vivendi)

;; More screen real estate
(scroll-bar-mode 0)
(tool-bar-mode 0)
(tooltip-mode 0) ; disable popup, make help text appear in echo area
(menu-bar-mode 0)
(column-number-mode t)
(set-fringe-mode '(5 . 13)) ;; describe variable fringe-mode
(global-display-line-numbers-mode 1) ; always show line numbers
(global-hl-line-mode +1)
(global-visual-line-mode +1) ; prefer Visual Line Mode
;; (add-hook 'text-mode-hook #'visual-line-mode) ; selectively, instead of global visual line mode
;; (add-hook 'org-mode-hook  #'visual-line-mode) ; selectively, instead of global visual line mode

;; Tweak Font sizes globally, and also set line number mode
(defun adi/set-frame-font--default ()
  "Interactively set default frame font for day to day work."
  (set-frame-font "-PfEd-DejaVu Sans Mono-normal-normal-normal-*-13-*-*-*-m-0-iso10646-1")
  (global-display-line-numbers-mode -1))

(defun adi/set-frame-font--pair-prog ()
  "Interactively set frame font for pair programming."
  (set-frame-font "-PfEd-DejaVu Sans Mono-normal-normal-normal-*-16-*-*-*-m-0-iso10646-1")
  (global-display-line-numbers-mode 1))

(defun adi/set-frame-font--code-demo ()
  "Interactively set frame font for presentations and demos."
  (set-frame-font "-1ASC-Liberation Mono-normal-normal-normal-*-28-*-*-*-m-0-iso10646-1")
  (global-display-line-numbers-mode -1))

;; Ensure we always start Emacs with the default font.

;; Package management

(require 'package)
;; Explicitly set the exact package archives list
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "https://orgmode.org/elpa/")
                         ("elpa" . "https://elpa.gnu.org/packages/")))
;; Set package download directory relative to the dotemacs-dir
(setq package-user-dir (file-name-as-directory
                        (expand-file-name "elpa" dotemacs-dir)))

(unless package-archive-contents

;; Use use-package

;; Ian Y.E. Pan's tutorial is a nice quick overview.
;; https://ianyepan.github.io/posts/setting-up-use-package/

(unless (package-installed-p 'use-package)
  (package-install 'use-package))

  (add-to-list 'load-path package-user-dir)
  (require 'use-package))

(setq use-package-always-ensure t)
(setq use-package-expand-minimally t) ; set nil to debug use-package forms

;; All the packages!

;; Packages useful to configure packages

;; radian-software/blackout to tune major/minor mode names
;; in modeline. It unifies functionality of the mutually
;; confusing modeline lighters delight, diminish, and dim.
;; It integrates seamlessly with use-package.
(use-package blackout)

(use-package helpful ; h/t systemcrafters.net
  ;; https://github.com/Wilfred/helpful
  :ensure t
  (setq helpful-max-buffers 1) ; but actually we want it to reuse buffer
  :bind (("C-h f" . #'helpful-callable)
         ("C-h F" . #'helpful-function) ; exclude macros
         ("C-h v" . #'helpful-variable)
         ("C-h k" . #'helpful-key)
         ("C-h x" . #'helpful-command)
         ;; Lookup the current symbol at point. C-c C-d is
         ;; a common keybinding for this in lisp modes.
         ("C-c C-d" . #'helpful-at-point))

(use-package command-log-mode
  :ensure t

;;; Remember states of files, buffer, desktop
(use-package recentf
  :ensure t
  (setq recentf-save-file (expand-file-name "recentf"
        recentf-max-saved-items 1000
        recentf-max-menu-items 10
        recentf-auto-cleanup 'never)
  (recentf-mode +1)

(use-package savehist
  (setq savehist-additional-variables
        '(search-ring regexp-search-ring)
        savehist-autosave-inerval 60
        savehist-file (expand-file-name "savehist"

(use-package saveplace
  (setq save-place-file
        (expand-file-name "saveplace" dotemacs-savefile-dir))
  (save-place-mode +1))

(use-package desktop
  (add-to-list 'desktop-path dotemacs-savefile-dir)
  (setq desktop-dirname dotemacs-savefile-dir
        desktop-auto-save-timeout 30)

  (defun adi/desktop-read-after-emacs-startup ()
    (when (file-exists-p
           (expand-file-name desktop-base-file-name desktop-dirname))
  ;; does not work with 'after-init-hook
  (add-hook 'emacs-startup-hook #'adi/desktop-read-after-emacs-startup)

  (desktop-save-mode 1))

(use-package super-save ; h/t bbatsov/prelude
  :ensure t
  (super-save-mode +1)

(use-package amx ; h/t Protesilaos Stavrou
  ;; NOTES: When amx is active:
  ;; - "C-h f" calls describe-function
  ;; - "M-." jumps to definition
  ;; - "C-h w" shows keybindings for thing at point
  :ensure t
  (setq amx-save-file
        (expand-file-name "amx-items" dotemacs-savefile-dir))
  (setq amx-backend 'ivy) ; integrates with counsel-M-x
  (setq amx-show-key-bindings t) ; t by default
  ;; (add-to-list 'amx-ignored-command-matchers "") ; to ignore commands
  (amx-mode +1)

;; Manage windows, buffers, movement, navigation and create a more
;; "heads up display" kind of experience.

(use-package counsel ; brings in ivy, and swiper too
  :ensure t
  (ivy-mode +1)
  (:map counsel-find-file-map
        ("C-l" . counsel-up-directory)
        :map global-map
        ("M-x" . counsel-M-x)
        ("C-c c" . counsel-company)
        ("C-c i" . counsel-imenu)
        ("C-x C-f" . counsel-find-file)
        ("C-c C-f" . counsel-recentf)
        ("C-c g" . counsel-git)         ; find files respecting gitignore
        ("C-c k" . counsel-ag)
        ("C-c l" . counsel-locate)
        ("C-c b" . counsel-switch-buffer-other-window))

(use-package swiper
  :ensure t
   ("C-s" . swiper)
   ("C-c t" . swiper-thing-at-point) ; swiper-map prefix key is "C-c"
   ("C-c a t" . swiper-all-thing-at-point)))

;; Note: Use "M-o" after "C-x C-f" or "C-s" for additional options
;; for the context for the thing selected in the minibuffer.
(use-package ivy
  :ensure t
  (ivy-mode t)
  (setq ivy-use-virtual-buffers t
        ivy-count-format "%d/%d: ")    ; per the docs

(use-package ivy-rich ; h/t suvratapte/dot-emacs-dot-d
  :ensure t
  (ivy-rich-path-style 'abbreviate)
  (ivy-rich-mode +1)

(use-package ibuffer
  :bind (:map global-map
              ("C-x C-b" . ibuffer-other-window))

(use-package which-key
  :ensure t
  :config (which-key-mode t)
  (setq which-key-idle-delay 0.5)
  ;; Sort based on the key description ignoring case (default
  ;; is 'which-key-key-order).
  (setq which-key-sort-order 'which-key-description-order)

(use-package ace-window
  :ensure t
  (:map global-map
        ("s-w" . 'ace-window)
        ([remap other-window] . 'ace-window)))

(use-package avy
  :ensure t)

(use-package key-chord
  :ensure t
  (key-chord-mode +1)

  (setq key-chord-one-key-delay 0.2)           ; e.g. "jj", default 0.2
  (setq key-chord-two-keys-delay 0.2)          ; e.g. "jk", default 0.1
  (setq key-chord-safety-interval-backward 0.5) ; default 0.1 is too close to key delays
  (setq key-chord-safety-interval-forward 0) ; default 0.35 causes laggy experience

  (key-chord-define-global "jj" 'avy-goto-word-1)
  (key-chord-define-global "jl" 'avy-goto-line)
  (key-chord-define-global "jk" 'avy-goto-char)
  (key-chord-define-global "XX" 'execute-extended-command)
  (key-chord-define-global "yy" 'counsel-yank-pop)
  (key-chord-define-global "g;" 'ace-window)
  (key-chord-define-global ";g" 'ace-window)

(use-package golden-ratio
  ;; https://github.com/roman/golden-ratio.el
  :ensure t
  (golden-ratio-mode +1)
  ;; Set auto scale to `t' for wide screens, but not for 16:10, 1900px HD displays
  (setq golden-ratio-auto-scale nil
        ;; golden-ratio-wide-adjust-factor 1
        ;; golden-ratio-adjust-factor .8
  ;; Make golden ratio play nice with other modes
  (dolist (cmd '(ace-window
    (add-to-list 'golden-ratio-extra-commands

(use-package rotate
  ;; https://github.com/daichirata/emacs-rotate
  :ensure t

;;; General text viewing and editing

;; Enable narrowings to enhance focus, and reduce accidental
;; edits of nonfocus areas (thanks to save-restrictions).
;; h/t bbatsov/prelude
;; Note: `C-x n w` makes all visible again.
(put 'narrow-to-region 'disabled nil)
(put 'narrow-to-page 'disabled nil)
(put 'narrow-to-defun 'disabled nil)

;; COMplete ANYthing, please!
;; TODO: instead try radian's completion system
;;; ref: https://github.com/radian-software/radian
(use-package company
  :bind (:map global-map
              ("TAB" . company-indent-or-complete-common)
              ;; Got to love the name hippie-expand. Use this for general
              ;; expansions, because company-complete is good for the
              ;; current/narrow-case expansions. By default, `M-/` binds
              ;; to the less powerful `dabbrev-expand`. To alter search
              ;; options, :config the hippie-expand-try-functions-list.
              ("M-/" . hippie-expand))
  :config (setq company-idle-delay 0.1
                company-minimum-prefix-length 2)
  (global-company-mode t)

;; Selections
(use-package expand-region
  :ensure t
  (("C-=" . er/expand-region)
   ("C-M-=" . er/contract-region)))

;; Multiple cursors
(use-package multiple-cursors
  :ensure t
  ;; Idea taken from "Emacs: Define Key Sequence"
  ;; ref: http://ergoemacs.org/emacs/emacs_keybinding_power_of_keys_sequence.html
  ;; define prefix keymap for multiple cursors
  (define-prefix-command 'adi/multi-cursor-keymap)
  (define-key adi/multi-cursor-keymap (kbd "e") 'mc/edit-lines)
  (define-key adi/multi-cursor-keymap (kbd "a") 'mc/mark-all-like-this-dwim)
  (define-key adi/multi-cursor-keymap (kbd "r") 'mc/mark-all-in-region-regexp)
  (define-key adi/multi-cursor-keymap (kbd "s") 'mc/mark-all-symbols-like-this-in-defun)
  (define-key adi/multi-cursor-keymap (kbd "w") 'mc/mark-all-words-like-this-in-defun)
  (define-key adi/multi-cursor-keymap (kbd "C-n") 'mc/mark-next-like-this)
  (define-key adi/multi-cursor-keymap (kbd "C-p") 'mc/mark-previous-like-this)
  (define-key adi/multi-cursor-keymap (kbd "C-a") 'mc/mark-all-like-this)
  :bind-keymap ("C-c m" . adi/multi-cursor-keymap))

(use-package wgrep ; editable grep buffers FTW!
  :ensure t)

;;; Lispy editing support

;; Tweak settings of built-in paren package
(use-package paren
  :ensure nil                    ; it already exists, don't try to search online
  (setq show-paren-delay 0)
  (show-paren-mode t)

(use-package paredit
  ;; Handy configs available at the wiki https://www.emacswiki.org/emacs/ParEdit
  ;; including combining with eldoc, "electric" enabled modes etc.
  :ensure t
  (("M-[" . paredit-wrap-square)
   ("M-{" . paredit-wrap-curly))
  :hook ((emacs-lisp-mode lisp-interaction-mode ielm-mode)
         . paredit-mode)

(use-package eldoc
  :ensure t
  (global-eldoc-mode t)
  :custom ; h/t jwiegley/dot-emacs
  (eldoc-echo-area-use-multiline-p t)
  (eldoc-echo-area-display-truncation-message nil)
  ;; for hooks, ref: https://www.emacswiki.org/emacs/ElDoc
  :hook ((emacs-lisp-mode lisp-interaction-mode ielm-mode)
         . eldoc-mode)

;;; Programming and Writing workflow support

(use-package magit
  :ensure t

(use-package projectile
  :ensure t

(use-package yasnippet
  :ensure t
  (defvar dotemacs-yasnippets-dir
    (file-name-as-directory (expand-file-name "snippets" dotemacs-dir)))
  (unless (file-exists-p dotemacs-yasnippets-dir)
    (make-directory dotemacs-yasnippets-dir))

  (setq yas-snippet-dirs
        (list dotemacs-yasnippets-dir))

  (add-to-list 'hippie-expand-try-functions-list

  (yas-global-mode +1)
  :blackout yas-minor-mode)

(use-package yasnippet-snippets
  :ensure t

(use-package flyspell
  :config (flyspell-mode +1))

(use-package flycheck
  :ensure t
  :config (global-flycheck-mode +1)

(provide 'init)
;;; init.el ends here

Our current place in the rough plan:

  • [✓] Set the very preliminaries.
  • [✓] Set up package management. I'll probably stick with the old familiars; elpa and melpa. I'm not sure about straight.el at this time.
  • [✓] Choose use-package to get and configure each package. I like how neat configs are, when defined with use-package.
  • [✓] Unexpectedly refactor the whole thing.
  • [✓] Make completions and "getting about" work (the right mix of ivy, consul, swiper, company, helm, imenu). Someone mentioned newer alternatives to helm. Have a look at that.
  • [✓] Fix general text editing stuff (keybindings, multiple cursors, snippets etc.)
  • [WIP] Add support for favourite programming languages.
    • [✓] Emacs Lisp
    • many others…
  • org-mode specifics
  • then let's see…

  1. None of this is any use if one is unable to touch-type. I believe touch typing is a core skill that pays off in so many contexts, not just in highly keyboard-friendly contexts like text editing with Emacs or Vim. If you are not a decent touch typist I suggest getting to about 40 words per minute proficiency, which I feel is good enough to reap the benefits of my kind of workflow.↩︎