Andrey Listopadov

Region bindings and common lisp modes

@Emacs Emacs-Lisp ~3 minutes read

I’ve published two new packages for Emacs: - region-bindings.el and common-lisp-modes.el. Both are quite small and were a part of my configuration for a long time, but after small refactoring of my init.el I’ve decided to extract them.

The first one, region-bindings is a from-scratch re-implementation of region-bindings-mode. This project had not seen development since 2014, and I had issues with how it works. When I tried it I wasn’t using transient-mark-mode (tmm), yet, that package still activated its bindings, and did not properly disable it. Even with tmm enabled, it managed to leak its state when I used isearch. So I decided to make my own.

The implementation is not too different, but I made sure that it works both with and without tmm. As an added bonus, it features a global minor mode and some pre-defined keybindings.

I mostly use it with puni and multiple-cursors.el:

(use-package puni
  :bind ( :map region-bindings-mode-map
          ("(" . puni-wrap-round)
          ("[" . puni-wrap-square)
          ("{" . puni-wrap-curly)
          ("<" . puni-wrap-angle)))

(use-package multiple-cursors
  :bind
  ( :map region-bindings-mode-map
    ("n" . mc/mark-next-symbol-like-this)
    ("N" . mc/mark-next-like-this)
    ("p" . mc/mark-previous-symbol-like-this)
    ("P" . mc/mark-previous-like-this)
    ("a" . mc/mark-all-symbols-like-this)
    ("A" . mc/mark-all-like-this)
    ("s" . mc/mark-all-in-region-regexp)
    ("l" . mc/edit-ends-of-lines)))

The second package is called “common lisp modes” but it has nothing to do with Common Lisp. It’s a very small package that provides a minor mode that you can use to set up other packages that are common between various lisps. So it’s not “common-lisp modes” but rather “common lisp-modes”.

The main reason is that the lisp-mode in Emacs is for Common Lisp and its derivatives that share the same syntax. Unfortunately, other lisps like Clojure or Fennel can’t inherit from lisp-mode and instead inherit their major modes from prog-mode. This works but comes at a cost that you can’t really add lisp-specific things that you want across all lisps, like paredit, to the prog-mode-hook because it will be enabled in other programming languages too, like in Erlang or OCaml, where it makes no sense. There’s a lisp-data-mode in Emacs, which can be used instead of prog-mode, but there are already too many packages implemented in terms of prog-mode and they don’t really need to change that, as they work fine.

The same goes for most of the REPLs - they use variants of comint-mode as a parent mode, and like prog-mode comint is used for much more than lisp REPLs. So again, it’s hard to configure common features specific to lisp REPLs only.

Thus this package provides two minor modes common-lisp-modes-mode and common-repl-modes-mode, to which you can add hooks. For example, my configuration does the following:

;; actual configs omitted
(use-package minibuffer
  :hook (eval-expression-minibuffer-setup . common-lisp-modes-mode))

(use-package elisp-mode
  :hook (emacs-lisp-mode . common-lisp-modes-mode))

(use-package fennel-mode
  :hook ((fennel-mode fennel-repl-mode) . common-lisp-modes-mode))

(use-package lisp-mode
  :hook ((lisp-mode lisp-data-mode) . common-lisp-modes-mode))

(use-package inf-lisp
  :hook (inferior-lisp-mode . common-lisp-modes-mode))

(use-package sly
  :hook (sly-mrepl-mode . common-lisp-modes-mode))

(use-package scheme
  :hook (scheme-mode . common-lisp-modes-mode))

(use-package racket-mode
  :hook ((racket-mode racket-repl-mode) . common-lisp-modes-mode))

(use-package clojure-mode
  :hook ((clojure-mode clojurec-mode clojurescript-mode) . common-lisp-modes-mode))

(use-package cider
  :hook (cider-repl-mode . common-lisp-modes-mode))

And then I simply use common-lisp-modes-mode-hook to enable common features:

(use-package puni
  :hook ((common-lisp-modes-mode nxml-mode) . puni-mode))

(use-package isayt
  :hook (common-lisp-modes-mode . isayt-mode))

Maybe there are not that many common modes in my configuration, but I still appreciate the fact that I don’t need to list all lisps I use in both puni and isayt configuration, like this, or keep a list somewhere else:

(use-package puni
  :hook ((eval-expression-minibuffer-setup-hook
          emacs-lisp-mode-hook
          fennel-mode-hook
          fennel-repl-mode-hook
          lisp-mode-hook
          lisp-data-mode-hook
          inferior-lisp-mode-hook
          sly-mrepl-mode-hook
          scheme-mode-hook
          racket-mode-hook
          racket-repl-mode-hook-hook
          clojure-mode-hook
          clojurec-mode-hook
          clojurescript-mode-hook
          cider-repl-mode-hook)
         . puni-mode))

(use-package isayt
  :hook ((eval-expression-minibuffer-setup-hook
          emacs-lisp-mode-hook
          fennel-mode-hook
          fennel-repl-mode-hook
          lisp-mode-hook
          lisp-data-mode-hook
          inferior-lisp-mode-hook
          sly-mrepl-mode-hook
          scheme-mode-hook
          racket-mode-hook
          racket-repl-mode-hook-hook
          clojure-mode-hook
          clojurec-mode-hook
          clojurescript-mode-hook
          cider-repl-mode-hook)
         . isayt-mode))

It is harder to maintain, as I’ll need to add new lisps I want to try out or remove lisps once I’m done playing with them to prevent cluttering. I already have a lot of lisps I use semi-regularly, so I want the process to be more seamless.

I hope these packages will be helpful for others too, but I don’t think I’m going to submit them to MELPA or (Non-GNU)ELPA. You can use straight.el, el-get, quelpa and such to install these directly from git.