Modal in Emacs, but not bloated!

started by alice 2026-07-01

I was playing a little with modal stuff, and wanted a more minimal version to use.

So, what is the best way to do this?
Should I extend viper-mode, use a package like ryo-mode or command or maybe edit an existing one like ergoemacs or xah-fly-keys?

I found that the best is just to define a kaybind map and a minor mode, then bind that as a toggle on a keybind.

So this is my own modal mode for emacs!

elisp
;; -*- lexical-binding: t; -*-

(defvar modal-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map [t] 'ignore)
    (dolist (binding '(

                       ("|"   . modal-mode)
                       ("\\"  . modal-mode)
                       ("g"   . keyboard-quit)
                       ("_"   . undo)
                       ("f"   . forward-char)
                       ("b"   . backward-char)
                       ("p"   . previous-line)
                       ("n"   . next-line)

                       ("D"   . duplicate-line)
                       ("N N" . my/move-line-down)
                       ("P P" . my/move-line-up)

                       ("m i" . imenu)
                       ("m d" . find-dired)
                       ("m m" . tmm-menubar)
                       ("m r" . recentf)
                       ("m b" . switch-to-buffer)
                       ("m f" . find-file)

                       ("M s" . bookmark-set)
                       ("M S" . bookmark-save)
                       ("M d" . bookmark-delete)
                       ("M j" . bookmark-jump)
                       ("M l" . bookmark-bmenu-list)

                       ("P x" . project-execute-extended-command)
                       ("P v" . project-vc-dir)
                       ("P s" . project-search)
                       ("P S" . project-eshell)
                       ("P c" . project-compile)
                       ("P b" . project-list-buffers)

                       ("h s" . eshell)
                       ("h a" . async-shell-command)

                       ("s g" . grep-find)
                       ("s q" . query-replace)
                       ("s o" . occur)
                       ("s O" . occur-edit-mode)
                       ("s s" . mark-sexp)
                       ("s w" . mark-word)
                       ("s d" . mark-defun)
                       ("s p" . mark-paragraph)
                       ("s e" . mark-end-of-sentence)
                       ("s x" . exchange-point-and-mark)

                       ("x d" . xref-find-definitions)
                       ("x r" . xref-find-references)
                       ("x b" . xref-go-back)
                       ("x x" . execute-extended-command)
                       ("x X" . execute-extended-command-for-buffer)

                       ("r v" . narrow-to-region)
                       ("r w" . widen)
                       ("r s" . rectangle-mark-mode)
                       ("r S" . set-mark-command)
                       ("r t" . string-rectangle)

                       ("h p" . highlight-symbol-at-point)
                       ("h r" . highlight-regexp)
                       ("h u" . unhighlight-regexp)

                       ("c r" . comint-run)
                       ("c s" . comint-send-region)

                       ("a a" . appt-add)
                       ("a c" . appt-check)
                       ("a d" . appt-delete)

                       ("e r" . re-builder)
                       ("e a" . apropos)
                       ("e d" . dictionary)
                       ("e c" . calendar)
                       ("e C" . calc)
                       ("e D" . diary)
                       ("e A" . org-agenda-list)

                       ("S 2" . split-window-below)
                       ("S 3" . split-window-right)
                       ("S 1" . delete-other-windows)
                       ("S 0" . delete-window)
                       ("S o" . other-window)

                       ("E s" . save-buffer)
                       ("E k" . kill-buffer)

                       ))
      (define-key map (kbd (car binding)) (cdr binding)))
    map)
  "Keymap for `modal-mode'.")

(define-minor-mode modal-mode
  "\
---------------------------------------------------------------------------------
A modal mode.

This is a super simple modal mode with the goal to remove modifier keys for
commonly used functions and grouping them with a common prefix key.

It uses the Emacs bindings as a base of inspiration, then strips the modifier
from it, and takes some liberties in how it does defines the new key if the
same key is used by multiple key-binds with different modifier keys.

`modal-mode' also add key-binds for functions I use frequently that are not
mapped by default. This may not be useful for normal people, but this is my own.
Why am I even writing this as it is just for myself? One may never know.
---------------------------------------------------------------------------------

"
  :lighter " am"
  :keymap modal-mode-map)

(add-hook 'modal-mode-hook (lambda () (if modal-mode (setq cursor-type 'bar) (setq cursor-type 'box))))

(global-set-key (kbd "C-c C-SPC") 'modal-mode)

(provide 'modal)

I have grouped the keybinds out from their categore, like h for highlight, e for extra orsforsearchandselect`.

If there are some more neat things I could add please tell!

That was all, just wanted to show how simple it can be.

I've been a fan of meow for some time, as it falls through gracefully to default emacs keys and is in the helix/kakoune style of editing.

Log in or register to reply.