Summary: lisp-mode with tree-sitter support
Requires: cond-star-, compat-31, emacs-30.2
Website: https://codeberg.org/zshaftel/lisp-ts-mode
Keywords: lisp languages tree-sitter
Maintainer: zach shaftel <[email protected]>
Author: zach shaftel <[email protected]>
━━━━━━━━━━━━━━
LISP-TS-MODE
━━━━━━━━━━━━━━
A tree-sitter alternative to `lisp-mode' which uses [this grammar].
Table of Contents
─────────────────
1. Why?
2. Getting started
3. `FORMAT' directive indentation
4. Font lock
[this grammar] <https://codeberg.org/zshaftel/tree-sitter-cl-syntax>
1 Why?
══════
The killer feature of this mode is the `FORMAT' string support. Using
a specialized embedded grammar for format strings enables format
directive font-lock and indentation. Trust me, the font-lock helps *a
lot* when writing intricate format strings. The indentation is more
niche and can be customized or simply disabled, see 3.
A sample, showing off both indentation and font-lock:
<file:format-screenshot.png>
If this makes your eyes bleed, you can customize all those faces to
look as boring as you'd like.
The highlighted `~{~}' directive after the cursor is from
`show-paren-mode', because `lisp-ts-mode''s
`syntax-propertize-function' adds delimiter syntax to format
directives. This also means `forward-sexp' moves across those
directives like it does on regular lists.
Aside from that, the truth is tree-sitter doesn't add nearly as much
to most Lisp languages *on its own* compared to other
languages. Emacs' syntax system is (unsurprisingly) extremely well
suited to handling Lisp syntax (notably, it even has first class
support for nested comments unlike tree-sitter). *But* the CST
generated by tree-sitter enables some /very/ fancy semantic font-lock,
see the sister package [gaudy-cl].
[gaudy-cl] <https://codeberg.org/zshaftel/gaudy-cl>
2 Getting started
═════════════════
This package is available on GNU ELPA, so here are some examples of
how to activate:
┌────
│ (use-package lisp-ts-mode
│ :ensure t)
└────
If you use [elpaca], you can use the following recipe:
┌────
│ (elpaca lisp-ts-mode)
└────
For [straight.el]:
┌────
│ (straight-use-package 'lisp-ts-mode)
└────
To automatically activate `lisp-ts-mode' wherever `lisp-mode' normally
activates, use
┌────
│ (setf (alist-get 'lisp-mode major-mode-remap-alist) 'lisp-ts-mode)
└────
You can add this to the `:init' section of `use-package'.
I also recommend this snippet:
┌────
│ (setf (alist-get 'lisp-ts-mode font-lock-ignore)
│ lisp-ts-mode-font-lock-ignore-keywords)
└────
You can place this in the `:config' section of `use-package'. This
disables font-lock keywords which are already handled by the
tree-sitter based font-lock rules.
Lastly, the `FORMAT' string support is a separate minor mode,
`lisp-ts-format-support-mode'. You can enable it with
┌────
│ (add-hook 'lisp-ts-mode-hook #'lisp-ts-format-support-mode)
└────
But if you use [gaudy-cl], it will apply the grammar on its own (and
much more).
[elpaca] <https://github.com/progfolio/elpaca>
[straight.el] <https://github.com/radian-software/straight.el>
[gaudy-cl] <https://codeberg.org/zshaftel/gaudy-cl>
3 `FORMAT' directive indentation
════════════════════════════════
There is optional (but currently enabled by default) auto-indentation
of format directives. Still somewhat experimental. This is mainly to
indent relative to the paired directives (`~[', `~{', `~(' and
`~<'). Example:
┌────
│ "~<
│ ~A
│ ~:>"
└────
After pressing `TAB' on the second line:
┌────
│ "~<~@
│ ~A
│ ~:>"
└────
The directive is indented to the column after the `~<', and the
newline is converted to a `~@<newline>' directive so that the
indentation of the output isn't affected. The behavior depends on a
few customizable variables:
`lisp-ts-mode-format-indent-predicate'
A predicate for `treesit-node-match-p' (usually a function or
regexp matching a treesit node's type) to determine which nodes
should have their /contents/ indented. By default it matches the
four paired directives, but you can also set it to indent
relative to the start of the entire format string. *To disable
format indentation entirely, set it to nil.*
`lisp-ts-mode-format-indent-auto-escape-eol'
Controls the behavior of the newline directive. It can be set to
nil (don't indent if there isn't already a `~<newline>'), t
(don't add it but indent anyway), a string specifying the
directive prefix to add, or a cons pair `(LOGICAL-BLOCK
. DEFAULT)' to specify a different string (like `~:@_~') to use
within `~<~:>'.
`lisp-ts-mode-format-indent-tilde-relative'
Determines which column is used as an anchor for calculating
indentation (using the following two variables), and the
indentation of the end of a paired directive when it begins a
line. If nil (the default), the directive characters themselves
are aligned, like
┌────
│ "~:@{
│ ~A
│ ~}"
└────
If set to non-nil, the ~s are aligned:
┌────
│ "~:@{
│ ~A
│ ~}"
└────
`lisp-ts-mode-format-group-indent-offset'
Number of columns added to indentation relative to the start of
a paired directive.
`lisp-ts-mode-format-string-indent-offset'
Like the above but controls indentation relative to the start of
the string, if that's enabled by
`lisp-ts-mode-format-indent-predicate'.
`lisp-ts-mode-format-indent-function'
This variable isn't customizable because it's meant to be used
with `add-function'. This is the function called to perform the
indentation, so you can wrap it to control if, when or how the
indentation is performed.
For more details see each variable's documentation.
4 Font lock
═══════════
There are distinct faces for every component of format directives I
could think of: `~', numeric, character, `V' and `#' parameters, the
`,' separator between parameters, `:' and `@' modifiers and the
directive character itself all have specific faces. The paired
directives (`~[', `~{', `~(' and `~<') have a separate face from other
directives, and you can customize
`lisp-ts-mode-format-rainbow-delimiters' to use [rainbow-delimiters]
faces to highlight those directives based on nesting level. There are
also three faces for each level of `#||#' comment nesting (the top
level uses `font-lock-comment-face'). See `M-x customize-group RET
lisp-ts-mode'.
[rainbow-delimiters] <https://github.com/Fanael/rainbow-delimiters>