clojure-ts-mode 
- Description
- Major mode for Clojure code
- Latest
- clojure-ts-mode-0.6.0snapshot0.20251003.205157.tar (.sig), 2025-Oct-04, 470 KiB
- Maintainer
- Bozhidar Batsov <bozhidar@batsov.dev>
- Website
- http://github.com/clojure-emacs/clojure-ts-mode
- Browse ELPA's repository
- CGit or Gitweb
- Badge
To install this package from Emacs, use package-install or list-packages.
Full description
Clojure Tree-sitter Mode
clojure-ts-mode is an Emacs major mode that provides font-lock (syntax
highlighting), indentation, and navigation support for the
Clojure(Script) programming language, powered by the
tree-sitter-clojure
Tree-sitter grammar.
Rationale
clojure-mode has served us well
for a very long time, but it suffers from a few long-standing
problems, related to
Emacs limitations baked into its design. The introduction of built-in support
for Tree-sitter in Emacs 29 presents a natural opportunity to address many of
them. Enter clojure-ts-mode, which makes use of Tree-sitter to provide:
- fast, accurate and more granular font-locking
- fast indentation
- common Emacs functionality like structured navigation,
imenu(an outline of a source buffer), current form inference (used internally by various Emacs modes and utilities), etc
Working with Tree-sitter is significantly easier than the legacy Emacs APIs for font-locking and
indentation, which makes it easier to contribute to clojure-ts-mode, and to improve it in general.
Keep in mind that the transition to clojure-ts-mode won't happen overnight for several reasons:
- getting to feature parity with
clojure-modewill take some time - tools that depend on
clojure-modewill need to be updated to work withclojure-ts-mode - we still need to support users of older Emacs versions that don't support Tree-sitter
That's why clojure-ts-mode is being developed independently of clojure-mode
and will one day replace it when the time is right. (e.g. 3 major Emacs version
down the road, so circa Emacs 32)
You can read more about the vision for clojure-ts-mode here.
Current Status
[!WARNING]
This library is still under active development. Breaking changes should be expected.
The currently provided functionality should cover the needs of most Clojure programmers, but you can expect to encounter some bugs and missing functionality here and there.
Those will be addressed over the time, as more and more people use clojure-ts-mode.
Installation
Requirements
For clojure-ts-mode to work, you need Emacs 30+ built with Tree-sitter support.
To check if your Emacs supports Tree-sitter run the following (e.g. by using M-:):
(treesit-available-p)
Additionally, you'll need to have Git and some C compiler (cc) installed and available
in your $PATH (or Emacs's exec-path), for clojure-ts-mode to be able to install the required
Tree-sitter grammars automatically.
[!TIP]
As the Tree-sitter support in Emacs is still fairly new and under active development itself, for optimal results you should use the latest stable Emacs release or even the development version of Emacs. See the "Caveats" section for more on the subject.
Install clojure-ts-mode
[!NOTE]
That's the recommended way to install
clojure-ts-mode.
If you have git and a C compiler (cc) available on your system's PATH,
clojure-ts-mode will install the
grammars
clojure-ts-mode is available on MElPA and NonGNU ELPA. It can be installed with:
(package-install 'clojure-ts-mode)
package-vc
Emacs also includes package-vc-install, so you can run:
(package-vc-install "https://github.com/clojure-emacs/clojure-ts-mode")
to install this package from source.
Manual installation
You can install it by cloning the repository and adding it to your load path.
git clone https://github.com/clojure-emacs/clojure-ts-mode.git
(add-to-list 'load-path "~/path/to/clojure-ts-mode/")
Once installed, evaluate clojure-ts-mode.el and you should be ready to go.
Install Tree-sitter grammars
[!NOTE]
clojure-ts-modeinstall the required grammars automatically, so for most people no manual actions will be required.
clojure-ts-mode makes use of the following Tree-sitter grammars:
- The experimental version Clojure grammar. This version includes a few
improvements, which potentially will be promoted to a stable release (See the
discussion). This grammar is required for proper work of
clojure-ts-mode. - markdown-inline, which will be used for docstrings if available and if
clojure-ts-use-markdown-inlineis enabled. - tree-sitter-regex, which will be used for regex literals if available and if
clojure-ts-use-regex-parseris notnil.
clojure-ts-clojurescript-mode can optionally use tree-sitter-javascript grammar
to highlight JS syntax in js* forms. This is enabled by default and can be
turned off by setting clojure-ts-clojurescript-use-js-parser to nil.
clojure-ts-jank-mode can optionally use tree-sitter-cpp grammar to highlight C++
syntax in native/raw forms. This is enabled by default and can be turned off by
setting clojure-ts-jank-use-cpp-parser to nil.
If you have git and a C compiler (cc) available on your system's PATH,
clojure-ts-mode will install the
grammars when you first open a Clojure file and clojure-ts-ensure-grammars is
set to t (the default). macOS users can install the required tools like this:
xcode-select --install
Similarly, Debian/Ubuntu users can do something like:
sudo apt install build-essential
This installs GCC, G++, make, and other essential development tools.
If clojure-ts-mode fails to automatically install the grammar, you have the
option to install it manually. Please, refer to the installation instructions of
each required grammar and make sure you're install the versions expected (see
clojure-ts-grammar-recipes for details).
If clojure-ts-ensure-grammars is enabled, clojure-ts-mode will try to upgrade
the Clojure grammar if it's outdated. This might happen, when you activate
clojure-ts-mode for the first time after package update. If grammar was
previously installed, you might need to restart Emacs, because it has to reload
the grammar binary.
Upgrading Tree-sitter grammars
To reinstall or upgrade Tree-sitter grammars, you can execute:
M-x clojure-ts-reinstall-grammars
This will install the latest compatible grammars, even if they are already installed.
Configuration
To see a list of available configuration options do M-x customize-group <RET> clojure-ts.
Most configuration changes will require reverting any active clojure-ts-mode buffers.
Remapping of clojure-mode buffers
By default, clojure-ts-mode assumes command over all buffers and file
extensions previously associated with clojure-mode (and derived major modes
like clojurescript-mode). To disable this remapping, set
(setopt clojure-ts-auto-remap nil)
You can also use the commands clojure-ts-activate / clojure-ts-deactivate to
interactively change this behavior.
Indentation
clojure-ts-mode currently supports 2 different indentation strategies:
semantic, the default, which tries to match the indentation ofclojure-modeandcljfmtfixed, a simple indentation strategy outlined by Tonsky in a blog post
Set the var clojure-ts-indent-style to change it.
(setopt clojure-ts-indent-style 'fixed)
[!TIP]
You can find this article comparing semantic and fixed indentation useful.
Customizing semantic indentation
The indentation of special forms and macros with bodies is controlled via
clojure-ts-semantic-indent-rules. Nearly all special forms and built-in macros
with bodies have special indentation settings in clojure-ts-mode, which are
aligned with cljfmt indent rules. You can add/alter the indentation settings in
your personal config. Let's assume you want to indent ->> and -> like this:
(->> something
ala
bala
portokala)
You can do so by putting the following in your config:
(setopt clojure-ts-semantic-indent-rules '(("->" . ((:block 1)))
("->>" . ((:block 1)))))
This means that the body of the ->/->> is after the first argument.
The default set of rules is defined as
clojure-ts--semantic-indent-rules-defaults, any rule can be overridden using
customization option.
Two types of rules are supported: :block and :inner, mirroring those in
cljfmt. When a rule is defined as :block n, n represents the number of
arguments preceding the body. When a rule is defined as :inner n, each form
within the expression's body, nested n levels deep, is indented by two
spaces. These rule definitions fully reflect the cljfmt rules.
For example:
dohas a rule((:block 0)).whenhas a rule((:block 1)).defnandfnhave a rule((:inner 0)).letfnhas a rule((:block 1) (:inner 2 0)).
Note that clojure-ts-semantic-indent-rules should be set using the
customization interface or setopt; otherwise, it will not be applied
correctly.
Project-specific indentation
Custom indentation rules can be set for individual projects. To achieve this,
you need to create a .dir-locals.el file in the project root. The content
should look like:
((clojure-ts-mode . ((clojure-ts-semantic-indent-rules . (("with-transaction" . ((:block 1)))
("with-retry" . ((:block 1))))))))
In order to apply directory-local variables to existing buffers, they must be "reverted" (reloaded).
Vertical alignment
You can vertically align sexps with C-c SPC. For instance, typing this combo
on the following form:
(def my-map
{:a-key 1
:other-key 2})
Leads to the following:
(def my-map
{:a-key 1
:other-key 2})
This can also be done automatically (as part of indentation) by turning on
clojure-ts-align-forms-automatically. This way it will happen whenever you
select some code and hit TAB.
Forms that can be aligned vertically are configured via the following variables:
clojure-ts-align-reader-conditionals- align reader conditionals as if they were maps.clojure-ts-align-binding-forms- a customizable list of forms with let-like bindings that can be aligned vertically.clojure-ts-align-cond-forms- a customizable list of forms whose body elements can be aligned vertically. These forms respect the block semantic indentation rule (if configured) and align only the body forms, skipping N special arguments.clojure-ts-align-separator- determines whether blank lines prevent vertical alignment.
Font Locking
To highlight entire rich comment expression with the comment font face, set
(setopt clojure-ts-comment-macro-font-lock-body t)
By default this is nil, so that anything within a comment expression is
highlighted like regular Clojure code.
[!TIP]
You can customize the exact level of font-locking via the variables
treesit-font-lock-level(the default value is 3) andtreesit-font-lock-features-list. Check this section of the Emacs manual for more details.
Extending font-lock rules
In clojure-ts-mode it is possible to specify additional defn-like forms that
should be fontified. For example to highlight the following form from Hiccup
library as a function definition:
(defelem file-upload
"Creates a file upload input."
[name]
(input-field "file" name nil))
You can add defelem to clojure-ts-extra-def-forms list like this:
(add-to-list 'clojure-ts-extra-def-forms "defelem")
or set this variable using setopt:
(setopt clojure-ts-extra-def-forms '("defelem"))
This setting will highlight defelem symbol, function name and the docstring.
[!IMPORTANT]
Setting
clojure-ts-extra-def-formswon't change the indentation rule for these forms. For indentation rules you should useclojure-ts-semantic-indent-rulesvariable (see semantic indentation section).
Highlight markdown syntax in docstrings
By default Markdown syntax is highlighted in the docstrings using
markdown-inline grammar. To disable this feature use:
(setopt clojure-ts-use-markdown-inline nil)
Example of Markdown syntax highlighting:

Highlight regular expression syntax
By default syntax inside regex literals is highlighted using regex grammar. To disable this feature use:
(setopt clojure-ts-use-regex-parser nil)
Example of regex syntax highlighting:

Navigation and Evaluation
To make forms inside of (comment ...) forms appear as top-level forms for evaluation and navigation, set
(setopt clojure-ts-toplevel-inside-comment-form t)
Fill paragraph
To change the maximal line length used by M-x prog-fill-reindent-defun (also
bound to M-q by default) to reformat docstrings and comments it's possible to
customize clojure-ts-fill-paragraph variable (by default set to the value of
Emacs' fill-paragraph value).
Every new line in the docstrings is indented by
clojure-ts-docstring-fill-prefix-width number of spaces (set to 2 by default
which matches the clojure-mode settings).
imenu
clojure-ts-mode supports various types of definition that can be navigated
using imenu, such as:
- namespace
- function
- macro
- var
- interface (forms such as
defprotocol,definterfaceanddefmulti) - class (forms such as
deftype,defrecordanddefstruct) - keyword (for example, spec definitions)
Integration with outline-minor-mode
clojure-ts-mode supports two integration variants with
outline-minor-mode. The default variant uses special top-level comments (level
1 heading starts with three semicolons, level 2 heading starts with four,
etc.). The other variant treats def-like forms (the same forms produced by the
imenu command) as outline headings. To use the second option, use the
following customization:
(setopt clojure-ts-outline-variant 'imenu)
Refactoring support
Threading macros related features
There are a bunch of commands for threading and unwinding threaded Clojure forms:
clojure-ts-thread: Thread another form into the surrounding thread. Both->>/some->>and->/some->variants are supported.clojure-ts-unwind: Unwind a threaded expression. Supports both->>/some->>and->/some->.clojure-ts-thread-first-all: Introduce the thread first macro (->) and rewrite the entire form. With a prefix argument do not thread the last form.clojure-ts-thread-last-all: Introduce the thread last macro and rewrite the entire form. With a prefix argument do not thread the last form.clojure-ts-unwind-all: Fully unwind a threaded expression removing the threading macro.
Customize threading refactoring behavior
By default clojure-ts-thread-first-all and clojure-ts-thread-last-all will
thread all nested expressions. For example this expression:
(->map (assoc {} :key "value") :lock)
After executing clojure-ts-thread-last-all will be converted to:
(-> {}
(assoc :key "value")
(->map :lock))
This behavior can be changed by setting:
(setopt clojure-ts-thread-all-but-last t)
Then the last expression will not be threaded and the result will be:
(-> (assoc {} :key "value")
(->map :lock))
Cycling things
clojure-ts-cycle-keyword-string: Convert the string at point to a keyword and vice versa.clojure-ts-cycle-privacy: Cycle privacy ofdefs ordefns. Use metadata explicitly with settingclojure-ts-use-metadata-for-defn-privacytotfordefns too.clojure-ts-cycle-conditional: Change a surrounding conditional form to its negated counterpart, or vice versa (supportsif/if-notandwhen/when-not). Forif/if-notalso transposes the else and then branches, keeping the semantics the same as before.clojure-ts-cycle-not: Add or remove anotform around the current form.
Convert collection
Convert any given collection at point to list, quoted list, map, vector or set. The following commands are available:
clojure-ts-convert-collection-to-listclojure-ts-convert-collection-to-quoted-listclojure-ts-convert-collection-to-mapclojure-ts-convert-collection-to-vectorclojure-ts-convert-collection-to-set
Add arity to a function or macro
clojure-ts-add-arity: Add a new arity to an existing single-arity or
multi-arity function or macro. Function can be defined using defn, fn or
defmethod form. This command also supports functions defined inside forms like
letfn, defprotol, reify, extend-protocol or proxy.
Default keybindings
| Keybinding | Command |
|---|---|
C-: |
clojure-ts-cycle-keyword-string |
C-c SPC |
clojure-ts-align |
C-c C-r t / C-c C-r C-t |
clojure-ts-thread |
C-c C-r u / C-c C-r C-u |
clojure-ts-unwind |
C-c C-r f / C-c C-r C-f |
clojure-ts-thread-first-all |
C-c C-r l / C-c C-r C-l |
clojure-ts-thread-last-all |
C-c C-r p / C-c C-r C-p |
clojure-ts-cycle-privacy |
C-c C-r ( / C-c C-r C-( |
clojure-ts-convert-collection-to-list |
C-c C-r ' / C-c C-r C-' |
clojure-ts-convert-collection-to-quoted-list |
C-c C-r { / C-c C-r C-{ |
clojure-ts-convert-collection-to-map |
C-c C-r [ / C-c C-r C-[ |
clojure-ts-convert-collection-to-vector |
C-c C-r # / C-c C-r C-# |
clojure-ts-convert-collection-to-set |
C-c C-r c / C-c C-r C-c |
clojure-ts-cycle-conditional |
C-c C-r o / C-c C-r C-o |
clojure-ts-cycle-not |
C-c C-r a / C-c C-r C-a |
clojure-ts-add-arity |
Customize refactoring commands prefix
By default prefix for all refactoring commands is C-c C-r. It can be changed
by customizing clojure-ts-refactor-map-prefix variable.
Code completion
clojure-ts-mode provides basic code completion functionality. Completion only
works for the current source buffer and includes completion of top-level
definitions and local bindings. This feature can be turned off by setting:
(setopt clojure-ts-completion-enabled nil)
Here's the short video illustrating the feature with Emacs's built-in completion UI (it
should also work well with more advanced packages like company and corfu):
https://github.com/user-attachments/assets/7c37179f-5a5d-424f-9bd6-9c8525f6b2f7
Migrating to clojure-ts-mode
If you are migrating to clojure-ts-mode note that clojure-mode is still
required for CIDER and clj-refactor packages to work properly.
After installing the package do the following:
- Check the value of
clojure-mode-hookand copy all relevant hooks toclojure-ts-mode-hook.
(add-hook 'clojure-ts-mode-hook #'cider-mode)
(add-hook 'clojure-ts-mode-hook #'enable-paredit-mode)
(add-hook 'clojure-ts-mode-hook #'rainbow-delimiters-mode)
(add-hook 'clojure-ts-mode-hook #'clj-refactor-mode)
- Update
.dir-locals.elin all of your Clojure projects to activate directory local variables inclojure-ts-mode.
((clojure-mode
(cider-clojure-cli-aliases . ":test:repl"))
(clojure-ts-mode
(cider-clojure-cli-aliases . ":test:repl")))
Caveats
As the Tree-sitter Emacs APIs are new and keep evolving there are some
differences in the behavior of clojure-ts-mode on different Emacs versions.
Here are some notable examples:
- On Emacs 29 the parent mode is
prog-mode, but on Emacs 30+ it's bothprog-modeandclojure-mode(this is very helpful when dealing withderived-mode-pchecks) - Navigation by sexp/lists might work differently on Emacs versions lower than 31. Starting with version 31, Emacs uses Tree-sitter 'things' settings, if available, to rebind some commands.
- If you set
clojure-ts-extra-def-forms,clojure-ts-modewill highlight the specified forms, including their docstrings, in a manner similar to Clojure'sdefn. However, Markdown syntax will not be highlighted within these custom docstrings.
Frequently Asked Questions
What clojure-mode features are currently missing?
As of version 0.5.x, clojure-ts-mode provides almost all clojure-mode features.
Currently only a few refactoring commands are missing.
Does clojure-ts-mode work with CIDER?
Yes! Preliminary support for clojure-ts-mode was released in CIDER
1.14. Note that
clojure-mode is still needed for some APIs that haven't yet been ported to
clojure-ts-mode.
For now, when you take care of the keybindings for the CIDER commands you use
and ensure cider-mode is enabled for clojure-ts-mode buffers in your config,
most functionality should already work:
(add-hook 'clojure-ts-mode-hook #'cider-mode)
Check out this article for more details.
[!NOTE]
The dynamic indentation feature in CIDER requires clojure-ts-mode 0.3+.
Does clojure-ts-mode work with inf-clojure?
Yes, it does. inf-clojure 3.3+ supports clojure-ts-mode.
Why does clojure-ts-mode require Emacs 30?
You might be wondering why does clojure-ts-mode require Emacs 30 instead of
Emacs 29, which introduced the built-in Tree-sitter support. The answer is
simple - the initial Tree-sitter support in Emacs 29 had quite a few issues and
we felt it's better to nudge most people interested in using it to Emacs 30,
which fixed a lot of the problems.
Contributing
We welcome contributions of any kind!
If you're not familiar with Tree-sitter, a good place to start is our design documentation, which explains how Tree-sitter works in Emacs in broad strokes and covers some of the design decisions we've made a long the way.
We're using Eldev as our build tool, so you'll have to install it. We also provide a simple Makefile with targets invoking Eldev. You only need to know a couple of them:
make lint
make test
The process of releasing a new version of clojure-ts-mode is documented here.
License
Copyright © 2022-2025 Danny Freeman, Bozhidar Batsov and contributors.
Distributed under the GNU General Public License; type C-h C-c to view it.
Old versions
| clojure-ts-mode-0.6.0snapshot0.20250815.44110.tar.lz | 2025-Aug-15 | 159 KiB |
| clojure-ts-mode-0.6.0snapshot0.20250808.94244.tar.lz | 2025-Aug-08 | 159 KiB |
| clojure-ts-mode-0.6.0snapshot0.20250705.130718.tar.lz | 2025-Jul-05 | 158 KiB |
| clojure-ts-mode-0.6.0snapshot0.20250626.65156.tar.lz | 2025-Jun-26 | 157 KiB |
| clojure-ts-mode-0.6.0snapshot0.20250617.113158.tar.lz | 2025-Jun-17 | 156 KiB |
| clojure-ts-mode-0.5.0snapshot0.20250531.70457.tar.lz | 2025-May-31 | 155 KiB |
| clojure-ts-mode-0.4.0.0.20250517.73454.tar.lz | 2025-May-17 | 153 KiB |
| clojure-ts-mode-0.3.0snapshot0.20250415.75830.tar.lz | 2025-Apr-15 | 48.2 KiB |
| clojure-ts-mode-0.2.4snapshot0.20250403.203904.tar.lz | 2025-Apr-04 | 46.2 KiB |
| clojure-ts-mode-0.1.5.0.20230924.94650.tar.lz | 2023-Sep-24 | 24.3 KiB |
News
Changelog
main (unreleased)
- Add a dedicated mode for editing Joker code. (
clojure-ts-joker-mode). - #113: Fix non-working refactoring commands for Emacs-30.
- #114: Extend built-in completion to complete keywords and local bindings in
foranddoseqforms. - #116: Extend built-in completion to complete all imported symbols from an
nsform. - Add documentation and bug reporting commands from
clojure-mode. - #118: Add some ns manipulation functions from
clojure-mode. - Fix a bug in
clojure-ts-add-aritywhen body has more than one expression. - #120: Fix a bug when symbols with metadata were not listed in imenu.
0.5.1 (2025-06-17)
- #109: Improve performance by pre-compiling Tree-sitter queries.
- #111: Set
clojure-ts-completion-at-point-functiononly forclojure-ts-modebuffers.
0.5.0 (2025-06-04)
- #96: Highlight function name properly in
extend-protocolform. - #96: Add support for extend-protocol forms to
clojure-ts-add-arityrefactoring command. - #99: Improve navigation by s-expression by switching to an experimental Clojure grammar.
- #99: More consistent docstrings highlighting and
fill-paragraphbehavior. - #99: Fix bug in
clojure-ts-alignwhen nested form has extra spaces. - #99: Fix bug in
clojure-ts-unwindwhen there is only one expression after threading symbol. - #103: Introduce
clojure-ts-jank-use-cpp-parsercustomization which allows highlighting C++ syntax in Janknative/rawforms. - #103: Introduce
clojure-ts-clojurescript-use-js-parsercustomization which allows highlighting JS syntax in ClojureScriptjs*forms. - #104: Introduce the
clojure-ts-extra-def-formscustomization option to specify additionaldefn-like forms that should be fontified. - #108: Introduce completion feature and
clojure-ts-completion-enabledcustomization.
0.4.0 (2025-05-15)
- #16: Introduce
clojure-ts-align. - #11: Enable regex syntax highlighting.
- #16: Add support for automatic aligning forms.
- #82: Introduce
clojure-ts-outline-variant. - #86: Better handling of function literals:
- Syntax highlighting of built-in keywords.
- Consistent indentation with regular forms.
- Support for automatic aligning forms.
- #88: Introduce
clojure-ts-unwindandclojure-ts-unwind-all. - #89: Introduce
clojure-ts-thread,clojure-ts-thread-first-allandclojure-ts-thread-last-all. - #90: Introduce
clojure-ts-cycle-privacy. - #91: Introduce
clojure-ts-cycle-keyword-string. ... ...