Automatically install and use tree-sitter major modes in Emacs 29+. If the tree-sitter version can’t be used, fall back to the original major mode.
Each of these behaviors are configurable and documented under the
“Configuration” section. By activating global-treesit-auto-mode
, Emacs will:
- Automatically switch to
<name>-ts-mode
when the grammar for<name>
is installed - Stick with
<name>-mode
if the grammar isn’t installed - (Optional) automatically install a grammar before opening a compatible file
There is also a convenience function M-x treesit-auto-install-all
, which will
install all of the maintained and compatible grammars.
treesit-auto
is available from MELPA. After following their setup, you can
use your preferred package manager. If that’s the default package.el
, simply
M-x package-refresh-contents
and then
M-x package-install RET treesit-auto
If you want a local clone of the repository, rather than just a copy of the
source, you might instead use package-vc-install
M-x package-vc-install RET https://github.com/renzmann/treesit-auto.git
Then, in your Emacs configuration file (~/.emacs.d/init.el
),
(use-package treesit-auto
:config
(global-treesit-auto-mode))
For most users, this will be enough. There are some nifty things you might want to enable, though, which are covered in the “Configuration” section below.
Emacs 29, while featuring treesit.el
and a convenient
treesit-install-language-grammar
, will not feature an intelligent way to choose
between a default mode, such as python-mode
, and its tree-sitter enhanced
version, python-ts-mode
. This package attempts to remedy that by adjusting the
major-mode-remap-alist
and treesit-language-source-alist
variables in order to
get the following behavior:
1. If the grammar is installed, then switch to the appropriate tree-sitter mode:
In this case, assuming we open a Python buffer, and the Python tree-sitter
grammar is installed, then Emacs will use python-ts-mode
instead of
python-mode
.
2. The grammar is NOT installed and treesit-auto-install is non-nil:
When the grammar is not installed and treesit-auto-install
is t, then upon
activating any major mode that has a corresponding tree-sitter mode, the grammar
will be downloaded and compiled using treesit-install-language-grammar
. Emacs
will then activate the tree-sitter major mode for that buffer.
prompt
is like t, except a message will be displayed in the echo area asking
for a yes/no response before attempting the installation.
As an example for both cases: if I visit a Python file and didn’t already have
the grammar installed, I wind up with an installed grammar and a buffer using
python-ts-mode
.
Otherwise, when treesit-auto-install
is nil, it will try to fall back to
another major mode as described in the following two rules.
3. If the grammar is NOT installed, and a fallback is specified
Most languages will have a fallback mode specified, such as python-ts-mode
falling back to python-mode
, if the grammar is not installed. If you ever need
to double-check what that fallback will be, you can double check what’s in the
recipe for that language like this:
(treesit-auto-recipe-remap (alist-get 'python treesit-auto-lang-recipe-alist)) ⇒ python-mode
See “Configuration/Configuring behavior for a specific language” in case you would like to specify different fallback modes than the default.
4. All other cases…
This is the most general case, where the grammar is not installed,
treesit-auto-install
is nil, and no fallback mode is specified in the language
recipe present on treesit-auto-recipe-list
. In this case, we still gain the
benefit of quickly installing grammars through treesit-install-language-grammar
without having the build the recipe interactively, but treesit-auto
will make no
attempt to switch away from the tree-sitter mode.
If you have modified treesit-language-source-alist
through setq
, then it is
recommended to put any configuration of this package AFTER that setq
.
The treesit-auto-install
variable controls whether a grammar should be installed
automatically when activating a major mode compatible with tree-sitter.
nil
, the default, meanstreesit-auto
won’t try to install anything, and will rely on the fallback logic outlined abovet
meanstreesit-auto
should always try to clone and install a grammar when missingprompt
will cause a yes/no prompt to appear in the minibuffer before attempting installation
(setq treesit-auto-install 'prompt)
Then, supposing I don’t have libtree-sitter-python.so
(or its mac/Windows
equivalent) under ~/.emacs.d/tree-sitter
(or anywhere else in
treesit-extra-load-path
), visiting a Python file or calling M-x python-ts-mode
will generate this prompt:
Tree-sitter grammar for python is missing. Would you like to install it from https://github.com/tree-sitter/tree-sitter-python? (y or n)
Responding with “yes” will use treesit-install-language-grammar
to go fetch and
compile the missing grammar.
The other function that respects this variable is treesit-auto-install-all
.
When treesit-auto-install
is t, using M-x treesit-auto-install-all
will skip all
prompts. Otherwise, it will ask before attempting the installation.
The variable treesit-auto-recipe-list
keeps track of all the language “recipes.”
These control how treesit-auto
decides which modes to upgrade/downgrade to/from,
where the source code of the language grammar is hosted, and which C/C++
compiler to use. Each recipe can take these arguments:
:lang :ts-mode :remap :url :revision :requires :source-dir :cc :c++
To create a recipe, use make-treesit-auto-recipe
:
(setq my-js-tsauto-config
(make-treesit-auto-recipe
:lang 'javascript
:ts-mode 'js-ts-mode
:remap '(js2-mode js-mode javascript-mode)
:url "https://github.com/tree-sitter/tree-sitter-javascript"
:revision "master"
:source-dir "src"))
(add-to-list 'treesit-auto-recipe-list my-js-tsauto-config)
Here, we’ve specified that the tree-sitter compiler will be creating a file
named libtree-sitter-javascript.so
(or .dylib
or .dll
), based on the :lang
field. The corresponding tree-sitter mode in Emacs is called js-ts-mode
, and
all of js2-mode
, js-mode
, and javascript-mode
should attempt switching to the
js-ts-mode
, if possible.
Moreover, since js-2-mode
is first under the :remap
section, that is the
“primary fallback.” Meaning that if the tree-sitter grammar is not available,
it will be the first mode tried. If that doesn’t work, it will try js-mode
, and
javascript-mode
, in that order, until one does work. If only one fallback needs
to be specified, a single quoted symbol is also acceptable. For instance,
python-ts-mode
just uses :remap 'python
in this argument position.
If a grammar mandates any other grammars be installed as a dependency, the
:requires
keyword can specify a language symbol or list of symbols that should
be installed. One example of this is found in the TypeScript recipe, which
specifies :requires 'tsx
, since activating typescript-ts-mode
on some Emacs
builds will attempt to load the TSX grammar.
The :url
, :revision
, :source-dir
, :cc
, and :c++
arguments are all documented
under treesit-language-source-alist
, which is part of base Emacs, not this
package.
This package does not modify any of your major mode hooks. That is, if you have
functions in python-mode-hook
, but not in python-ts-mode-hook
, then your hook
from python-mode
will not be applied, assuming python-ts-mode
is what gets
loaded. For major modes in which this is a concern, the current recommendation
is to address this as part of your configuration.
(setq rust-ts-mode-hook rust-mode-hook)
Some modes have a shared base, such as python-ts-mode
and python-mode
both
deriving from python-base-mode
. For these languages, you can opt to hook into
python-base-mode-hook
instead of explicitly setting the tree-sitter mode’s hook.
This is how I configure treesit-auto
for my own personal use.
(use-package treesit-auto
:demand t
:config
(setq treesit-auto-install 'prompt)
(global-treesit-auto-mode))
This package is, admittedly, a hack. treesit.el
provides an excellent
foundation to incremental source code parsing for Emacs 29, and over time that
foundation will expand into an improved core editing experience. With that in
mind, I fully expect this package to eventually be obsolesced by the default
options in Emacs 30 and beyond. That does not preclude us from adding a few
quality of life improvements to Emacs 29, though, and so it still seems prudent
to have this plugin available in the meantime.
Bug reports, feature requests, and contributions are most welcome. Even though this is a small project, there is always room for improvement. I also appreciate “nitpicky” contributions, such as formatting, conventions, variable naming, code simplification, and improvements to language in documentation.
Issues are tracked on GitHub, which is also where patches and pull requests should be submitted.
If you would like to submit a new language recipe to be distributed as part of this package, see CONTRIBUTING.md for a quick guide on how to write and submit the new recipe.