Emacs Redux: expreg: Expand Region, Reborn

Wait 5 sec.

expand-region is one of my alltime favorite Emacs packages. I’ve been using it since forever – press a key,the selection grows to the next semantic unit, press again, it grows further.Simple, useful, and satisfying. I’ve mentioned itquite a few times over the years, and it’s been apermanent fixture in my config for as long as I can remember.But lately I’ve been wondering if there’s a better way. I’ve been playing withNeovim and Helix from time to time (heresy, I know), and both have structuralselection baked in via tree-sitter – select a node, expand to its parent, and soon. Meanwhile, I’ve been building and using more tree-sitter major modes inEmacs (e.g. clojure-ts-mode andneocaml), and the contrast started tobother me. We have this rich AST sitting right there in the buffer, butexpand-region doesn’t know about it.The fundamental limitation is that expand-region relies on hand-written,mode-specific expansion functions for each language. Someone has to write andmaintain er/mark-ruby-block, er/mark-python-statement, er/mark-html-tag,and so on. Some languages have great support, others get generic fallbacks. Andwhen a new language comes along, you’re on your own until someone writes theexpansion functions for it.Enter Tree-sitterYou can probably see where this is going. Tree-sitter gives us a complete ASTfor any language that has a grammar, and walking up the AST from a point isexactly what semantic region expansion needs to do. Instead of hand-writtenheuristics per language, you just ask tree-sitter: “what’s the parent node?”Both Clojure and OCaml have rich, deeply nested syntax (s-expressions inClojure, pattern matching and nested modules in OCaml), and semantic selectionexpansion is invaluable when navigating their code. Rolling my own tree-sitterbased solution crossed my mind, but fortunately someone had already done itbetter.expregexpreg (short for “expand region”)1 is apackage by Yuan Fu – the same person who implemented Emacs’s built-intree-sitter support. Yuan Fu created expreg in mid-2023, shortly after thetree-sitter integration shipped in Emacs 29, and it landed on GNU ELPA in August It’s a natural extension of his tree-sitter work: if you’ve given Emacs aproper understanding of code structure, you might as well use it for selectiontoo.The package requires Emacs 29.1+ and the setup is minimal:(use-package expreg :ensure t :bind (("C-=" . expreg-expand) ("C--" . expreg-contract)))That’s it. Two commands: expreg-expand and expreg-contract. If you’ve usedexpand-region, you already know the workflow.What Makes It Better Than expand-regionIt works with any tree-sitter grammar automatically. No per-languageconfiguration. If your buffer has a tree-sitter parser active, expreg walks theAST to generate expansion candidates. OCaml, Clojure, Rust, Go, Python, C –all covered with zero language-specific code.It works without tree-sitter too. This is the key insight that makes expreg atrue expand-region replacement rather than just a tree-sitter toy. When notree-sitter parser is available, it falls back to Emacs’s built-in syntax tablesfor words, lists, strings, comments, and defuns. So it works infundamental-mode, text-mode, config files, commit messages – everywhere.It generates all candidate regions upfront. On the first call toexpreg-expand, every expander function runs and produces a list of candidateregions. These are filtered, sorted by size, and deduplicated. Subsequent callsjust pop the next region off the stack. This makes the behavior predictable andeasy to debug – no more wondering why expand-region jumped to somethingunexpected.It’s tiny. The entire package is a single file of a few hundred lines. Comparethat to expand-region’s dozens of mode-specific files. Less code means fewerbugs and easier maintenance.CustomizationThe expander functions are controlled by expreg-functions:(expreg--subword ; CamelCase subwords (when subword-mode is active) expreg--word ; words and symbols expreg--list ; parenthesized expressions expreg--string ; string literals expreg--treesit ; tree-sitter nodes expreg--comment ; comments expreg--paragraph-defun) ; paragraphs and function definitionsThere’s also expreg--sentence available but not enabled by default – usefulfor prose:(add-hook 'text-mode-hook (lambda () (add-to-list 'expreg-functions #'expreg--sentence)))Should You Switch?If you’re using tree-sitter based major modes (and in 2026, you probably shouldbe), expreg gives you better, language-aware expansion for free. If you’re stillon classic major modes, it’s at least as good as expand-region thanks to thesyntax-table fallbacks.The only reason to stick with expand-region is if you rely on some very specificmode-specific expansion behavior that expand-region’s hand-written functionshandle and expreg’s generic approach doesn’t. In practice, I haven’t hit such acase.I’ve been using expreg for a while now across Clojure, OCaml, Emacs Lisp, andvarious non-programming buffers. It just works. It’s one of those smallpackages that quietly improves your daily editing without demanding attention.And like tree-sitter powered code completion, it’s another example of howtree-sitter is reshaping what Emacs packages can do with minimal effort.That’s all I have for you today. Keep hacking! Naming is hard. ↩