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.
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:
imenu
(an outline of a source buffer), current form inference (used internally by various Emacs modes and utilities), etcWorking 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:
clojure-mode
will take some timeclojure-mode
will need to be updated to work with clojure-ts-mode
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.
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
.
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-:
):
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.
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)
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.
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.
Note
clojure-ts-mode
install 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:
clojure-ts-mode
.clojure-ts-use-markdown-inline
is enabled.clojure-ts-use-regex-parser
is not nil
.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:
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.
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.
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.
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.
clojure-ts-mode
currently supports 2 different indentation strategies:
semantic
, the default, which tries to match the indentation of clojure-mode
and cljfmt
fixed
, a simple indentation strategy outlined by Tonsky in a blog postSet 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 indentationThe 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:
do
has a rule ((:block 0))
.when
has a rule ((:block 1))
.defn
and fn
have a rule ((:inner 0))
.letfn
has 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.
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).
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.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) and treesit-font-lock-features-list
. Check this section of the Emacs manual for more details.
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-forms
won't change the indentation rule for these forms. For indentation rules you should use clojure-ts-semantic-indent-rules
variable (see semantic indentation section).
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 syntaxBy 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 EvaluationTo make forms inside of (comment ...)
forms appear as top-level forms for evaluation and navigation, set
(setopt clojure-ts-toplevel-inside-comment-form t)
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).
clojure-ts-mode
supports various types of definition that can be navigated using imenu
, such as:
defprotocol
, definterface
and defmulti
)deftype
, defrecord
and defstruct
)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)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.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))
clojure-ts-cycle-keyword-string
: Convert the string at point to a keyword and vice versa.clojure-ts-cycle-privacy
: Cycle privacy of def
s or defn
s. Use metadata explicitly with setting clojure-ts-use-metadata-for-defn-privacy
to t
for defn
s too.clojure-ts-cycle-conditional
: Change a surrounding conditional form to its negated counterpart, or vice versa (supports if
/if-not
and when
/when-not
). For if
/if-not
also transposes the else and then branches, keeping the semantics the same as before.clojure-ts-cycle-not
: Add or remove a not
form around the current form.Convert any given collection at point to list, quoted list, map, vector or set. The following commands are available:
clojure-ts-convert-collection-to-list
clojure-ts-convert-collection-to-quoted-list
clojure-ts-convert-collection-to-map
clojure-ts-convert-collection-to-vector
clojure-ts-convert-collection-to-set
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
.
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.
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
):
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:
clojure-mode-hook
and copy all relevant hooks to clojure-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)
.dir-locals.el
in all of your Clojure projects to activate directory local variables in clojure-ts-mode
.((clojure-mode (cider-clojure-cli-aliases . ":test:repl")) (clojure-ts-mode (cider-clojure-cli-aliases . ":test:repl")))
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:
prog-mode
, but on Emacs 30+ it's both prog-mode
and clojure-mode
(this is very helpful when dealing with derived-mode-p
checks)clojure-ts-extra-def-forms
, clojure-ts-mode
will highlight the specified forms, including their docstrings, in a manner similar to Clojure's defn
. However, Markdown syntax will not be highlighted within these custom docstrings.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.
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+.
Doesclojure-ts-mode
work with inf-clojure
?
Yes, it does. inf-clojure
3.3+ supports clojure-ts-mode
.
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.
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:
The process of releasing a new version of clojure-ts-mode
is documented here.
Copyright © 2022-2025 Danny Freeman, Bozhidar Batsov and contributors.
Distributed under the GNU General Public License; type C-h C-c to view it.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4