Keybinding Managers

Which-key

Which key is, I think, one of my favorite quality of life package. When you begin a keybind, Emacs will show you all keybinds you can follow the first one with in order to form a full keychord. Very useful when you have a lot of keybinds and don’t remember exactly what is what.

(use-package which-key
  :straight (:build t)
  :defer t
  :init (which-key-mode)
  :diminish which-key-mode
  :config
  (setq which-key-idle-delay 1))

General

General is an awesome package for managing keybindings. Not only is it oriented towards keychords by default (which I love), but it also provides some integration with evil so that we can declare keybindings for certain states only! This is a perfect replacement for define-key, evil-define-key, and any other function for defining keychords. And it is also possible to declare a prefix for my keybindings! By default, all keybinds will be prefixed with SPC and keybinds related to a specific mode (often major modes) will be prefixed by a comma , (and by C-SPC and M-m respectively when in insert-mode or emacs-mode). You can still feel some influence from my Spacemacs years here.

(use-package general
  :straight (:build t)
  :init
  (general-auto-unbind-keys)
  :config
  (general-create-definer phundrak/undefine
    :keymaps 'override
    :states '(normal emacs))
  (general-create-definer phundrak/evil
    :states '(normal))
  (general-create-definer phundrak/leader-key
    :states '(normal insert visual emacs)
    :keymaps 'override
    :prefix "SPC"
    :global-prefix "C-SPC")
  (general-create-definer phundrak/major-leader-key
    :states '(normal insert visual emacs)
    :keymaps 'override
    :prefix ","
    :global-prefix "M-m"))

Evil

Evil emulates most of vim’s keybinds, because let’s be honest here, they are much more comfortable than Emacs’.

(use-package evil
  :straight (:build t)
  :after (general)
  :init
  (setq evil-want-integration t
        evil-want-keybinding nil
        evil-want-C-u-scroll t
        evil-want-C-i-jump nil)
  (require 'evil-vars)
  (evil-set-undo-system 'undo-tree)
  :config
  <<evil-undefine-keys>>
  <<evil-bepo>>
  (evil-mode 1)
  (setq evil-want-fine-undo t) ; more granular undo with evil
  (evil-set-initial-state 'messages-buffer-mode 'normal)
  (evil-set-initial-state 'dashboard-mode 'normal))

I want to undefine some default keybinds of Evil because it does not match my workflow. Namely, I use the space key and the comma as leaders for my keybinds, and I’m way too used to Emacs’ C-t, C-a, C-e, and C-y.

(evil-global-set-key 'motion "t" 'evil-next-visual-line)
(evil-global-set-key 'motion "s" 'evil-previous-visual-line)

(general-define-key
 :keymaps 'evil-motion-state-map
 "SPC" nil
 ","   nil)
(general-define-key
 :keymaps 'evil-insert-state-map
 "C-t" nil)
(general-define-key
 :keymaps 'evil-insert-state-map
 "U"   nil
 "C-a" nil
 "C-y" nil
 "C-e" nil)

Something else that really bugs me is I use the bépo layout, which is not at all like the qwerty layout. For instance, hjkl becomes ctsr. Thus, I need some bépo-specific changes.

(dolist (key '("c" "C" "t" "T" "s" "S" "r" "R" "h" "H" "j" "J" "k" "K" "l" "L"))
  (general-define-key :states 'normal key nil))

(general-define-key
 :states 'motion
 "h" 'evil-replace
 "H" 'evil-replace-state
 "j" 'evil-find-char-to
 "J" 'evil-find-char-to-backward
 "k" 'evil-substitute
 "K" 'evil-smart-doc-lookup
 "l" 'evil-change
 "L" 'evil-change-line

 "c" 'evil-backward-char
 "C" 'evil-window-top
 "t" 'evil-next-visual-line
 "T" 'evil-join
 "s" 'evil-previous-visual-line
 "S" 'evil-lookup
 "r" 'evil-forward-char
 "R" 'evil-window-bottom)

This package enables and integrates Evil into a lot of different modes, such as org-mode, dired, mu4e, etc. Again, I need some additional code compared to most people due to the bépo layout.

(use-package evil-collection
  :after evil
  :straight (:build t)
  :config
  ;; bépo conversion
  (defun my/bépo-rotate-evil-collection (_mode mode-keymaps &rest _rest)
    (evil-collection-translate-key 'normal mode-keymaps
      ;; bépo ctsr is qwerty hjkl
      "c" "h"
      "t" "j"
      "s" "k"
      "r" "l"
      ;; add back ctsr
      "h" "c"
      "j" "t"
      "k" "s"
      "l" "r"))
  (add-hook 'evil-collection-setup-hook #'my/bépo-rotate-evil-collection)
  (evil-collection-init))

undo-tree is my preferred way of undoing and redoing stuff. The main reason is it doesn’t create a linear undo/redo history, but rather a complete tree you can navigate to see your complete editing history. One of the two obvious things to do are to tell Emacs to save all its undo history files in a dedicated directory, otherwise we’d risk littering all of our directories. The second thing is to simply globally enable its mode.

(use-package undo-tree
  :defer t
  :straight (:build t)
  :custom
  (undo-tree-history-directory-alist
   `(("." . ,(expand-file-name (file-name-as-directory "undo-tree-hist")
                               user-emacs-directory))))
  :init
  (global-undo-tree-mode)
  :config
  <<undo-tree-ignore-text-properties>>
  <<undo-tree-compress-files>>
  (setq undo-tree-visualizer-diff       t
        undo-tree-visualizer-timestamps t
        undo-tree-auto-save-history     t
        undo-tree-enable-undo-in-region t
        undo-limit        (* 800 1024)
        undo-strong-limit (* 12 1024 1024)
        undo-outer-limit  (* 128 1024 1024)))

An interesting behaviour from DoomEmacs is to compress the history files with zstd when it is present on the system. Not only do we enjoy much smaller files (according to DoomEmacs, we get something like 80% file savings), Emacs can load them much faster than the regular files. Sure, it uses more CPU time uncompressing these files, but it’s insignificant, and it’s still faster than loading a heavier file.

(when (executable-find "zstd")
  (defun my/undo-tree-append-zst-to-filename (filename)
    "Append .zst to the FILENAME in order to compress it."
    (concat filename ".zst"))
  (advice-add 'undo-tree-make-history-save-file-name
              :filter-return
              #'my/undo-tree-append-zst-to-filename))

Hydra

Hydraopen in new window is a simple menu creator for keybindings.

(use-package hydra
  :straight (:build t)
  :defer t)