# Combobulate: tree-sitter navigation and editing inside Emacs

> Combobulate is an Emacs package that adds structured movement and editing to programming major modes by using the tree-sitter library built into Emacs 29, so the buffer is navigated by syntax tree rather than by regular expressions. It suits an Emacs user who wants sexp-style movement in Python, JavaScript, TypeScript, JSON, YAML, TOML, HTML and CSS, and it needs the right grammar version to behave.

**mickeynp/combobulate** — Structured Editing and Navigation in Emacs with Tree-Sitter

- Repository: https://github.com/mickeynp/combobulate
- Stars: 1,253 · Forks: 97
- Language: Emacs Lisp
- License: GPL-3.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/mickeynp-combobulate

## Why a concrete syntax tree changes what movement means

Combobulate is a package that adds structured editing and movement to a wide range of programming languages, and the mechanism is what makes it different from the usual Emacs solutions. Most programming major modes determine what is what in your code using error-prone imperative code and regular expressions. Combobulate instead uses the tree-sitter library that ships with Emacs 29. Tree-sitter maintains a concrete syntax tree of the code, and the documentation's phrasing is that this gives Combobulate absolute clarity of all aspects of the code, enabling more correct movement and editing than you would otherwise have.

That word, concrete, matters. An abstract syntax tree describes meaning, which is what a compiler works with, and it discards formatting, comments and the exact source text. A concrete syntax tree keeps the source as written, which is what an editor needs, because when you move a node in a concrete tree the text moves with it, comments included, and when you splice one you splice the real bytes rather than a reconstructed approximation. That is the reason the package can offer to drag logical pieces of code up or down regardless of size or complexity, and the reason it can splice a code fragment into its parent node while removing it from where it was.

The design philosophy in the README is worth reading as a set of constraints, because it explains the shape of the package. It must be extensible and relatively easy to add new languages, which is why each language lives in its own combobulate-<lang>.el file that you open to see how the mode is defined. Commands should broadly work the same across all languages. There is a strong emphasis on extending and enriching existing movement and editing commands rather than inventing a parallel set. And it must have a shallow learning curve. That last constraint explains why the package feels like Emacs rather than like a plugin bolted onto it.

## The commands you already know, but tree-sitter aware

Combobulate is described as a companion to the existing movement and editing facilities in Emacs, designed to sympathetically improve or supplant editing and movement in many major modes that are otherwise lacking or incorrect. The specific improvements named in the README are concrete.

Navigating up or down list structures with C-M-u and C-M-d is much improved, whether you are moving in or out of structures in Python or in JSX elements in TypeScript or JavaScript. The list-like navigation bound to C-M-n and C-M-p also improves, because those commands now understand hierarchical code much better and will navigate between statements in code as well as parameters in functions or key pairs in dictionaries.

Beyond movement, the package adds a range of commands. There is region expansion that works much like the popular expand-regions package, expanding one syntactically interesting unit at a time, first the string the point is in, then the list it is in, and so on. There is code cloning, and when Combobulate cannot guess the exact node you want to clone it asks you to interactively preview and pick. There is splicing, which can keep some of your HTML tags and splice them into the parent node removing the parent node in the process. And there is drag and drop for logical code units, so if you can navigate between elements with C-M-p and C-M-n you can drag them around with M-P and M-N.

The pattern to notice is that the new commands are built on the same movement primitives you already have. That is the practical expression of the design philosophy, and it is why someone who knows C-M-n and C-M-p in a Lisp mode will find the package immediately legible.

## The query builder, the carousel and the field editor

Three features go beyond movement. The query builder is a way of searching that operates on the syntax tree rather than on text, and the article series linked from the README covers it separately, which suggests it is the part with the most depth. The carousel interface is for interactive node editing, and the article about it explains that it makes it possible to preview complex edits to your buffer before accepting them. That preview step is what makes a destructive operation safe, because splicing a large subtree is not something you want to discover after the fact.

The field editor and multiple cursors solve a specific problem. Combobulate can place cursors, either using the optional multiple cursors package or using the built-in field editor, at syntactically important points in your code. The examples given are dictionary elements, function arguments, and attributes in JSX elements. The difference between the two routes is worth knowing before you install anything. The multiple cursors package is optional and gives you several independent points, while the built-in field editor is part of Combobulate and gives you a field style editing surface for the node you are in. The README also has an article on bulk editing tree-sitter nodes with multiple cursors, covering the field editor and multiple cursors together.

For a bulk edit, that is the difference between editing a form field and editing a JSON document. If your work involves renaming keys across a configuration file, adding a field to a set of objects, or changing a repeated argument in a long call, this is the part of the package that has no obvious equivalent in stock Emacs.

## Language support depends on the grammar version, not just the mode

Getting Combobulate to work properly has two requirements, and the second one catches people. The package has to support your programming language, and it should ideally be the right grammar version, as the table explains. The warning is explicit: if you use a newer or older version you may run into issues, and not just in Combobulate, but in Emacs itself.

The supported list gives major modes alongside the grammar version each expects. CSS is covered by css-mode and css-ts-mode at v0.20.0. HTML is covered by html-mode, html-ts-mode, mhtml-mode and sgml-mode at v0.20.1. JSON is covered by json-mode and json-ts-mode at v0.20.2. The repository layout shows the rest of the range, with separate files for combobulate-css.el, combobulate-html.el, combobulate-js-ts.el, combobulate-json.el, combobulate-ocaml.el, combobulate-python.el, combobulate-toml.el and combobulate-yaml.el, alongside the shared combobulate-navigation.el, combobulate-manipulation.el, combobulate-procedure.el, combobulate-display.el, combobulate-ui.el and the rest.

The file naming is the extension mechanism made visible. The philosophy point number one says that to add a language you open combobulate-<lang>.el to see how the mode is defined, which means a language definition is a data structure describing the tree-sitter node types to treat as lists, symbols and other structural roles, rather than a pile of motion functions. That is what makes adding a language relatively easy, and it is also what makes the grammar version table necessary, because the node type names come from the grammar.

## The Docker image and the test harness for Emacs 29

The repository is set up for reproducible testing, which matters for a package that depends on a specific Emacs version and a specific tree-sitter build. The Dockerfile starts from a pinned ubuntu:26.04 image and installs the build dependencies, including libtree-sitter-dev, libjansson-dev, libncurses-dev, libgnutls28-dev and libxml2-dev. It then builds Emacs itself, with the version defaulting to 29.4 and build arguments for the source ref and the resolved commit SHA, and the comment explains the reasoning: release lanes use GNU tarballs, prerelease lanes use the official Savannah repository and supply both a named ref and its separately resolved full commit SHA, and consuming the SHA makes it part of the Docker layer cache identity. The build also validates that the SHA is a forty character hex string before using it.

The Makefile wraps that into the workflow. The byte-compile target runs batch-byte-compile over the Lisp sources, run-tests runs the ERT suite in batch mode with a no init file, and run-tests-junit produces a JUnit report for CI. There are also build-tests and clean-tests targets, the latter removing generated harness files, and docker-build for the image. A separate rebuild-relationships target runs a Python script from the build/ directory, and download-relationships does the same with a download flag, which is how the tree-sitter grammars and their relationships are managed. The .ts-setup.el and .ts-test.el files at the top level are the tree-sitter setup and test entry points.

## Where Combobulate is the wrong tool

Three cases argue against it. The first is a new Emacs user. The entire value proposition is that Combobulate improves the commands you already know, which means you have to already know them. On a fresh install with no muscle memory for C-M-n, C-M-p or C-M-u, the package is a set of extra bindings with no anchor, and the shallow learning curve the design philosophy promises assumes an Emacs user. The second is a language that is not in the table. The package supports a specific list with specific grammar versions, and a project in a language outside that list gets nothing, while a project that mixes several languages has to keep several grammars at matching versions, which is exactly the situation the documentation warns about.

The third is a stability expectation. The README opens with a warning that Combobulate is under active development, that you should expect bugs, and that the development branch is usually ahead of the master branch. That is an unusually candid statement to find at the top of a README, and it is worth taking at face value. It means the master branch you install from a package archive is not the newest work, and the newest work is where the bugs are. A configuration that pins a version works, but you are pinning a moving target and should expect to re-pin.

There is also the practical cost of a tree-sitter based workflow, which is that the grammars are a separate dependency. Combobulate does not bundle the grammars, it consumes them, and a grammar that is out of sync with the parser Emacs links against produces errors in Emacs itself and not only in Combobulate. The project is GPL-3.0 licensed, and the repository carries a LICENSE file, a Makefile, a build directory, a docs directory and a tests directory with the fixtures and harnesses. The last push to master was on 2026-10-14, and the repository publishes no GitHub releases, so the version history lives in the commits and in whatever package archive you install from.

## Conclusion

Combobulate suits an Emacs user who already lives in movement commands and wants them to understand syntax rather than indentation, and it is the most complete tree-sitter editing layer available inside Emacs today. It does not suit someone new to Emacs, since the whole value is in how it composes with existing motion and editing commands, and it does not suit a project whose language is not in the supported table. Before you install it, check that the grammar version listed for your language matches the tree-sitter grammar you have installed, because the documentation warns that a mismatched version causes problems in Combobulate and in Emacs itself, and note that the README says to expect bugs and that the development branch is usually ahead of master.

## FAQ

### What does Combobulate add to Emacs?

It adds structured movement and editing to programming major modes using the tree-sitter library in Emacs 29, so navigation works on a concrete syntax tree rather than on regular expressions. It improves commands like C-M-u, C-M-d, C-M-n and C-M-p and adds dragging, region expansion, cloning, splicing and cursor placement at syntactic points.

### Which languages does Combobulate support?

The README lists languages with their major modes and required grammar versions, including CSS at v0.20.0, HTML at v0.20.1 and JSON at v0.20.2, and the repository carries per language files such as combobulate-python.el, combobulate-js-ts.el, combobulate-yaml.el, combobulate-toml.el and combobulate-ocaml.el.

### Do I need a matching tree-sitter grammar version?

The documentation says the grammar version should ideally be right, and warns that a newer or older version may cause issues, not just in Combobulate but in Emacs itself. The version to use is listed per language in the support table.

### What is the Combobulate carousel and field editor?

The carousel is an interface for interactive node editing that lets you preview complex edits to your buffer before accepting them, and the field editor is a built-in alternative to the optional multiple cursors package for placing cursors at syntactically important points such as dictionary elements, function arguments or JSX attributes.

## Sources

- [Issues](https://github.com/mickeynp/combobulate/issues)
- [License: GPL-3.0](https://github.com/mickeynp/combobulate/blob/master/LICENSE)
- [mickeynp/combobulate on GitHub](https://github.com/mickeynp/combobulate)
- [README](https://github.com/mickeynp/combobulate/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mickeynp-combobulate
