- Notifications
You must be signed in to change notification settings - Fork18
The next generation Clojure major mode for Emacs, powered by TreeSitter
License
clojure-emacs/clojure-ts-mode
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
clojure-ts-mode
is an Emacs major mode that provides font-lock (syntaxhighlighting), indentation, and navigation support for theClojure(Script) programming language, powered by thetree-sitter-clojureTree-sitter grammar.
clojure-mode has served us wellfor a very long time, but it suffers from a fewlong-standingproblems, related toEmacs limitations baked into its design. The introduction of built-in supportfor Tree-sitter in Emacs 29 presents a natural opportunity to address many ofthem. Enterclojure-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 ofa source buffer), current form inference (used internally by various Emacsmodes and utilities), etc
Working with Tree-sitter is significantly easier than the legacy Emacs APIs for font-locking andindentation, which makes it easier to contribute toclojure-ts-mode
, and to improve it in general.
Keep in mind that the transition toclojure-ts-mode
won't happen overnight for several reasons:
- getting to feature parity with
clojure-mode
will take some time - tools that depend on
clojure-mode
will 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 whyclojure-ts-mode
is being developed independently ofclojure-mode
and will one day replace it when the time is right. (e.g. 3 major Emacs versiondown the road, so circa Emacs 32)
You can read more about the vision forclojure-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 youcan expect to encounter some bugs and missing functionality here and there.
Those will be addressed over the time, as more and more people useclojure-ts-mode
.
Forclojure-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 usingM-:
):
(treesit-available-p)
Additionally, you'll need to have Git and some C compiler (cc
) installed and availablein your$PATH
(or Emacs'sexec-path
), forclojure-ts-mode
to be able to install the requiredTree-sitter grammars automatically.
Tip
As the Tree-sitter support in Emacs is still fairly new and under active development itself, for optimalresults 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 installclojure-ts-mode
.
If you havegit
and a C compiler (cc
) available on your system'sPATH
,clojure-ts-mode
will install thegrammars
clojure-ts-mode is available onMElPA andNonGNU ELPA.It can be installed with:
(package-install'clojure-ts-mode)
Emacs also includespackage-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, evaluateclojure-ts-mode.el
and you should be ready to go.
Note
clojure-ts-mode
install the required grammars automatically, so for mostpeople no manual actions will be required.
clojure-ts-mode
makes use of the following Tree-sitter grammars:
- Theexperimental version Clojure grammar. This version includes a fewimprovements, which potentially will be promoted to a stable release (Seethediscussion). 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-inline
is enabled. - tree-sitter-regex, which will be used for regex literals if available and if
clojure-ts-use-regex-parser
is notnil
.
clojure-ts-clojurescript-mode
can optionally usetree-sitter-javascript
grammarto highlight JS syntax injs*
forms. This is enabled by default and can beturned off by settingclojure-ts-clojurescript-use-js-parser
tonil
.
clojure-ts-jank-mode
can optionally usetree-sitter-cpp
grammar to highlight C++syntax innative/raw
forms. This is enabled by default and can be turned off bysettingclojure-ts-jank-use-cpp-parser
tonil
.
If you havegit
and a C compiler (cc
) available on your system'sPATH
,clojure-ts-mode
will install thegrammars when you first open a Clojure file andclojure-ts-ensure-grammars
isset tot
(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.
Ifclojure-ts-mode
fails to automatically install the grammar, you have theoption to install it manually. Please, refer to the installation instructions ofeach required grammar and make sure you're install the versions expected (seeclojure-ts-grammar-recipes
for details).
Ifclojure-ts-ensure-grammars
is enabled,clojure-ts-mode
will try to upgradethe Clojure grammar if it's outdated. This might happen, when you activateclojure-ts-mode
for the first time after package update. If grammar waspreviously installed, you might need to restart Emacs, because it has to reloadthe 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 alreadyinstalled.
To see a list of available configuration options doM-x customize-group <RET> clojure-ts
.
Most configuration changes will require reverting any activeclojure-ts-mode
buffers.
By default,clojure-ts-mode
assumes command over all buffers and fileextensions previously associated withclojure-mode
(and derived major modeslikeclojurescript-mode
). To disable this remapping, set
(setopt clojure-ts-auto-remapnil)
You can also use the commandsclojure-ts-activate
/clojure-ts-deactivate
tointeractively change this behavior.
clojure-ts-mode
currently supports 2 different indentation strategies:
semantic
, the default, which tries to match the indentation ofclojure-mode
andcljfmt
fixed
,a simple indentation strategy outlined by Tonsky in a blog post
Set the varclojure-ts-indent-style
to change it.
(setopt clojure-ts-indent-style'fixed)
Tip
You can findthis article comparing semantic and fixed indentation useful.
The indentation of special forms and macros with bodies is controlled viaclojure-ts-semantic-indent-rules
. Nearly all special forms and built-in macroswith bodies have special indentation settings in clojure-ts-mode, which arealigned with cljfmt indent rules. You can add/alter the indentation settings inyour 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 '(("->". ((:block1))) ("->>". ((:block1)))))
This means that the body of the->
/->>
is after the first argument.
The default set of rules is defined asclojure-ts--semantic-indent-rules-defaults
, any rule can be overridden usingcustomization option.
Two types of rules are supported::block
and:inner
, mirroring those incljfmt. When a rule is defined as:block n
,n
represents the number ofarguments preceding the body. When a rule is defined as:inner n
, each formwithin the expression's body, nestedn
levels deep, is indented by twospaces. These rule definitions fully reflect thecljfmt rules.
For example:
do
has a rule((:block 0))
.when
has a rule((:block 1))
.defn
andfn
have a rule((:inner 0))
.letfn
has a rule((:block 1) (:inner 2 0))
.
Note thatclojure-ts-semantic-indent-rules
should be set using thecustomization interface orsetopt
; otherwise, it will not be appliedcorrectly.
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 contentshould look like:
((clojure-ts-mode. ((clojure-ts-semantic-indent-rules. (("with-transaction". ((:block1))) ("with-retry". ((:block1))))))))
In order to apply directory-local variables to existing buffers, they must be"reverted" (reloaded).
You can vertically align sexps withC-c SPC
. For instance, typing this comboon the following form:
(defmy-map {:a-key1:other-key2})
Leads to the following:
(defmy-map {:a-key1:other-key2})
This can also be done automatically (as part of indentation) by turning onclojure-ts-align-forms-automatically
. This way it will happen whenever youselect some code and hitTAB
.
Forms that can be aligned vertically are configured via the following variables:
clojure-ts-align-reader-conditionals
- align reader conditionals as if theywere maps.clojure-ts-align-binding-forms
- a customizable list of forms with let-likebindings that can be aligned vertically.clojure-ts-align-cond-forms
- a customizable list of forms whose bodyelements can be aligned vertically. These forms respect the block semanticindentation rule (if configured) and align only the body forms, skipping Nspecial arguments.clojure-ts-align-separator
- determines whether blank lines prevent verticalalignment.
To highlight entire richcomment
expression with the comment font face, set
(setopt clojure-ts-comment-macro-font-lock-bodyt)
By default this isnil
, so that anything within acomment
expression ishighlighted like regular Clojure code.
Tip
You can customize the exact level of font-locking via the variablestreesit-font-lock-level
(the default value is 3) andtreesit-font-lock-features-list
. Checkthissectionof the Emacs manual for more details.
Inclojure-ts-mode
it is possible to specify additional defn-like forms thatshould be fontified. For example to highlight the following form from Hiccuplibrary as a function definition:
(defelemfile-upload"Creates a file upload input." [name] (input-field"file" namenil))
You can adddefelem
toclojure-ts-extra-def-forms
list like this:
(add-to-list'clojure-ts-extra-def-forms"defelem")
or set this variable usingsetopt
:
(setopt clojure-ts-extra-def-forms '("defelem"))
This setting will highlightdefelem
symbol, function name and the docstring.
Important
Settingclojure-ts-extra-def-forms
won't change the indentation rule forthese forms. For indentation rules you should useclojure-ts-semantic-indent-rules
variable (seesemanticindentation section).
By default Markdown syntax is highlighted in the docstrings usingmarkdown-inline
grammar. To disable this feature use:
(setopt clojure-ts-use-markdown-inlinenil)
Example of Markdown syntax highlighting:
By default syntax inside regex literals is highlighted usingregex grammar. To disablethis feature use:
(setopt clojure-ts-use-regex-parsernil)
Example of regex syntax highlighting:
To make forms inside of(comment ...)
forms appear as top-level forms for evaluation and navigation, set
(setopt clojure-ts-toplevel-inside-comment-formt)
To change the maximal line length used byM-x prog-fill-reindent-defun
(alsobound toM-q
by default) to reformat docstrings and comments it's possible tocustomizeclojure-ts-fill-paragraph
variable (by default set to the value ofEmacs'fill-paragraph
value).
Every new line in the docstrings is indented byclojure-ts-docstring-fill-prefix-width
number of spaces (set to 2 by defaultwhich matches theclojure-mode
settings).
clojure-ts-mode
supports various types of definition that can be navigatedusingimenu
, such as:
- namespace
- function
- macro
- var
- interface (forms such as
defprotocol
,definterface
anddefmulti
) - class (forms such as
deftype
,defrecord
anddefstruct
) - keyword (for example, spec definitions)
clojure-ts-mode
supports two integration variants withoutline-minor-mode
. The default variant uses special top-level comments (level1 heading starts with three semicolons, level 2 heading starts with four,etc.). The other variant treats def-like forms (the same forms produced by theimenu
command) as outline headings. To use the second option, use thefollowing customization:
(setopt clojure-ts-outline-variant'imenu)
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 (->
) andrewrite 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 theentire form. With a prefix argument do not thread the last form.clojure-ts-unwind-all
: Fully unwind a threaded expression removing thethreading macro.
By defaultclojure-ts-thread-first-all
andclojure-ts-thread-last-all
willthread all nested expressions. For example this expression:
(->map (assoc {}:key"value"):lock)
After executingclojure-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-lastt)
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 andvice versa.clojure-ts-cycle-privacy
: Cycle privacy ofdef
s ordefn
s. Use metadataexplicitly with settingclojure-ts-use-metadata-for-defn-privacy
tot
fordefn
s too.clojure-ts-cycle-conditional
: Change a surrounding conditional form to itsnegated counterpart, or vice versa (supportsif
/if-not
andwhen
/when-not
). Forif
/if-not
also transposes the else and thenbranches, keeping the semantics the same as before.clojure-ts-cycle-not
: Add or remove anot
form around the current form.
Convert any given collection at point to list, quoted list, map, vector orset. 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 ormulti-arity function or macro. Function can be defined usingdefn
,fn
ordefmethod
form. This command also supports functions defined inside forms likeletfn
,defprotol
,reify
,extend-protocol
orproxy
.
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 |
By default prefix for all refactoring commands isC-c C-r
. It can be changedby customizingclojure-ts-refactor-map-prefix
variable.
clojure-ts-mode
provides basic code completion functionality. Completion onlyworks for the current source buffer and includes completion of top-leveldefinitions and local bindings. This feature can be turned off by setting:
(setopt clojure-ts-completion-enablednil)
Here's the short video illustrating the feature with Emacs's built-in completion UI (itshould also work well with more advanced packages likecompany
andcorfu
):
completion.mp4
If you are migrating toclojure-ts-mode
note thatclojure-mode
is stillrequired for CIDER andclj-refactor
packages to work properly.
After installing the package do the following:
- Check the value of
clojure-mode-hook
and 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.el
in all of your Clojure projects to activate directorylocal variables inclojure-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 somedifferences in the behavior ofclojure-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-mode
andclojure-mode
(this is very helpful when dealing withderived-mode-p
checks) - Navigation by sexp/lists might work differently on Emacs versions lowerthan 31. Starting with version 31, Emacs uses Tree-sitter 'things' settings, ifavailable, to rebind some commands.
- If you set
clojure-ts-extra-def-forms
,clojure-ts-mode
will highlight thespecified forms, including their docstrings, in a manner similar to Clojure'sdefn
. However, Markdown syntax will not be highlighted within these customdocstrings.
As of version 0.5.x,clojure-ts-mode
provides almost allclojure-mode
features.Currently only a few refactoring commands are missing.
Yes! Preliminary support forclojure-ts-mode
was released inCIDER1.14. Note thatclojure-mode
is still needed for some APIs that haven't yet been ported toclojure-ts-mode
.
For now, when you take care of the keybindings for the CIDER commands you useand ensurecider-mode
is enabled forclojure-ts-mode
buffers in your config,most functionality should already work:
(add-hook'clojure-ts-mode-hook#'cider-mode)
Check outthis article for more details.
Note
The dynamic indentation feature in CIDER requires clojure-ts-mode 0.3+.
Yes, it does.inf-clojure
3.3+ supportsclojure-ts-mode
.
You might be wondering why doesclojure-ts-mode
require Emacs 30 instead ofEmacs 29, which introduced the built-in Tree-sitter support. The answer issimple - the initial Tree-sitter support in Emacs 29 had quite a few issues andwe 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 ourdesign documentation, which explains how Tree-sitterworks in Emacs in broad strokes and covers some of the designdecisions we've made a long the way.
We're usingEldev as our build tool, so you'llhave to install it. We also provide a simpleMakefile with targets invoking Eldev. Youonly need to know a couple of them:
make lintmaketest
The process of releasing a new version ofclojure-ts-mode
is documentedhere.
Copyright © 2022-2025 Danny Freeman, Bozhidar Batsov andcontributors.
Distributed under the GNU General Public License; typeC-h C-c to view it.
About
The next generation Clojure major mode for Emacs, powered by TreeSitter
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.