@@ -197,6 +197,22 @@ double quotes on the third column."
197197 :safe #'listp
198198 :type '(repeat string))
199199
200+ (defcustom clojure-ts-align-forms-automatically nil
201+ " If non-nil, vertically align some forms automatically.
202+
203+ Automatically means it is done as part of indenting code. This applies
204+ to binding forms (`clojure-ts-align-binding-forms' ), to cond
205+ forms (`clojure-ts-align-cond-forms' ) and to map literals. For
206+ instance, selecting a map a hitting
207+ \\ <clojure-ts-mode-map>`\\[indent-for-tab-command]' will align the
208+ values like this:
209+
210+ {:some-key 10
211+ :key2 20}"
212+ :package-version '(clojure-ts-mode . " 0.4" )
213+ :safe #'booleanp
214+ :type 'boolean )
215+
200216(defvar clojure-ts-mode-remappings
201217 '((clojure-mode . clojure-ts-mode)
202218 (clojurescript-mode . clojure-ts-clojurescript-mode)
@@ -1340,6 +1356,9 @@ if NODE has metadata and its parent has type NODE-TYPE."
13401356 ((parent-is " vec_lit" ) parent 1 ) ; ; https://guide.clojure.style/#bindings-alignment
13411357 ((parent-is " map_lit" ) parent 1 ) ; ; https://guide.clojure.style/#map-keys-alignment
13421358 ((parent-is " set_lit" ) parent 2 )
1359+ ((parent-is " splicing_read_cond_lit" ) parent 4 )
1360+ ((parent-is " read_cond_lit" ) parent 3 )
1361+ ((parent-is " tagged_or_ctor_lit" ) parent 0 )
13431362 ; ; https://guide.clojure.style/#body-indentation
13441363 (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2 )
13451364 ; ; https://guide.clojure.style/#threading-macros-alignment
@@ -1447,40 +1466,67 @@ Regular expression and syntax analysis code is borrowed from
14471466
14481467BOUND bounds the whitespace search."
14491468 (unwind-protect
1450- (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point ) t )))
1451- (goto-char (treesit-node-start cur-sexp))
1452- (if (and (string= " sym_lit" (treesit-node-type cur-sexp))
1453- (clojure-ts--metadata-node-p (treesit-node-child cur-sexp 0 t ))
1454- (and (not (treesit-node-child-by-field-name cur-sexp " value" ))
1455- (string-empty-p (clojure-ts--named-node-text cur-sexp))))
1456- (treesit-end-of-thing 'sexp 2 'restricted )
1457- (treesit-end-of-thing 'sexp 1 'restrict ))
1458- (when (looking-at " ," )
1459- (forward-char ))
1460- ; ; Move past any whitespace or comment.
1461- (search-forward-regexp " \\ ([,\s\t ]*\\ )\\ (;+.*\\ )?" bound)
1462- (pcase (syntax-after (point ))
1463- ; ; End-of-line, try again on next line.
1464- (`(12 ) (clojure-ts--search-whitespace-after-next-sexp root-node bound))
1465- ; ; Closing paren, stop here.
1466- (`(5 . , _ ) nil )
1467- ; ; Anything else is something to align.
1468- (_ (point ))))
1469+ (let ((regex " \\ ([,\s\t ]*\\ )\\ (;+.*\\ )?" ))
1470+ ; ; If we're on an empty line, we should return match, otherwise
1471+ ; ; `clojure-ts-align-separator' setting won't work.
1472+ (if (and (bolp ) (looking-at-p " [[:blank:]]*$" ))
1473+ (progn
1474+ (search-forward-regexp regex bound)
1475+ (point ))
1476+ (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point ) t )))
1477+ (goto-char (treesit-node-start cur-sexp))
1478+ (if (and (string= " sym_lit" (treesit-node-type cur-sexp))
1479+ (clojure-ts--metadata-node-p (treesit-node-child cur-sexp 0 t ))
1480+ (and (not (treesit-node-child-by-field-name cur-sexp " value" ))
1481+ (string-empty-p (clojure-ts--named-node-text cur-sexp))))
1482+ (treesit-end-of-thing 'sexp 2 'restricted )
1483+ (treesit-end-of-thing 'sexp 1 'restrict ))
1484+ (when (looking-at " ," )
1485+ (forward-char ))
1486+ ; ; Move past any whitespace or comment.
1487+ (search-forward-regexp regex bound)
1488+ (pcase (syntax-after (point ))
1489+ ; ; End-of-line, try again on next line.
1490+ (`(12 ) (progn
1491+ (forward-char 1 )
1492+ (clojure-ts--search-whitespace-after-next-sexp root-node bound)))
1493+ ; ; Closing paren, stop here.
1494+ (`(5 . , _ ) nil )
1495+ ; ; Anything else is something to align.
1496+ (_ (point ))))))
14691497 (when (and bound (> (point ) bound))
14701498 (goto-char bound))))
14711499
1472- (defun clojure-ts--get-nodes-to-align (region-node beg end )
1500+ (defun clojure-ts--region-node (beg end )
1501+ " Return the smallest node that covers buffer positions BEG to END."
1502+ (let* ((root-node (treesit-buffer-root-node 'clojure )))
1503+ (treesit-node-descendant-for-range root-node beg end t )))
1504+
1505+ (defun clojure-ts--node-from-sexp-data (beg end sexp )
1506+ " Return updated node using SEXP data in the region between BEG and END."
1507+ (let* ((new-region-node (clojure-ts--region-node beg end))
1508+ (sexp-beg (marker-position (plist-get sexp :beg-marker )))
1509+ (sexp-end (marker-position (plist-get sexp :end-marker ))))
1510+ (treesit-node-descendant-for-range new-region-node
1511+ sexp-beg
1512+ sexp-end
1513+ t )))
1514+
1515+ (defun clojure-ts--get-nodes-to-align (beg end )
14731516 " Return a plist of nodes data for alignment.
14741517
1475- The search is limited by BEG, END and REGION-NODE .
1518+ The search is limited by BEG, END.
14761519
14771520Possible node types are: map, bindings-vec, cond or read-cond.
14781521
14791522The returned value is a list of property lists. Each property list
14801523includes `:sexp-type' , `:node' , `:beg-marker' , and `:end-marker' .
14811524Markers are necessary to fetch the same nodes after their boundaries
14821525have changed."
1483- (let* ((query (treesit-query-compile 'clojure
1526+ ; ; By default `treesit-query-capture' captures all nodes that cross the range.
1527+ ; ; We need to restrict it to only nodes inside of the range.
1528+ (let* ((region-node (clojure-ts--region-node beg end))
1529+ (query (treesit-query-compile 'clojure
14841530 (append
14851531 `(((map_lit) @map)
14861532 ((list_lit
@@ -1492,7 +1538,8 @@ have changed."
14921538 (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym)))
14931539 @cond))
14941540 (when clojure-ts-align-reader-conditionals
1495- '(((read_cond_lit) @read-cond)))))))
1541+ '(((read_cond_lit) @read-cond)
1542+ ((splicing_read_cond_lit) @read-cond)))))))
14961543 (thread-last (treesit-query-capture region-node query beg end)
14971544 (seq-remove (lambda (elt ) (eq (car elt) 'sym )))
14981545 ; ; When first node is reindented, all other nodes become
@@ -1542,43 +1589,44 @@ between BEG and END."
15421589 (end (clojure-ts--end-of-defun-pos)))
15431590 (list start end)))))
15441591 (setq end (copy-marker end))
1545- (let* ((root-node (treesit-buffer-root-node 'clojure ))
1546- ; ; By default `treesit-query-capture' captures all nodes that cross the
1547- ; ; range. We need to restrict it to only nodes inside of the range.
1548- (region-node (treesit-node-descendant-for-range root-node beg (marker-position end) t ))
1549- (sexps-to-align (clojure-ts--get-nodes-to-align region-node beg (marker-position end))))
1592+ (let* ((sexps-to-align (clojure-ts--get-nodes-to-align beg (marker-position end)))
1593+ ; ; We have to disable it here to avoid endless recursion.
1594+ (clojure-ts-align-forms-automatically nil ))
15501595 (save-excursion
15511596 (indent-region beg (marker-position end))
15521597 (dolist (sexp sexps-to-align)
15531598 ; ; After reindenting a node, all other nodes in the `sexps-to-align'
15541599 ; ; list become outdated, so we need to fetch updated nodes for every
15551600 ; ; iteration.
1556- (let* ((new-root-node (treesit-buffer-root-node 'clojure ))
1557- (new-region-node (treesit-node-descendant-for-range new-root-node
1558- beg
1559- (marker-position end)
1560- t ))
1561- (sexp-beg (marker-position (plist-get sexp :beg-marker )))
1562- (sexp-end (marker-position (plist-get sexp :end-marker )))
1563- (node (treesit-node-descendant-for-range new-region-node
1564- sexp-beg
1565- sexp-end
1566- t ))
1601+ (let* ((node (clojure-ts--node-from-sexp-data beg (marker-position end) sexp))
15671602 (sexp-type (plist-get sexp :sexp-type ))
15681603 (node-end (treesit-node-end node)))
15691604 (clojure-ts--point-to-align-position sexp-type node)
1570- (align-region (point ) node-end nil
1605+ (align-region (point ) node-end t
15711606 `((clojure-align (regexp . ,(lambda (&optional bound _noerror )
1572- (clojure-ts--search-whitespace-after-next-sexp node bound)))
1607+ (let ((updated-node (clojure-ts--node-from-sexp-data beg (marker-position end) sexp)))
1608+ (clojure-ts--search-whitespace-after-next-sexp updated-node bound))))
15731609 (group . 1 )
15741610 (separate . , clojure-ts-align-separator )
15751611 (repeat . t )))
15761612 nil )
15771613 ; ; After every iteration we have to re-indent the s-expression,
15781614 ; ; otherwise some can be indented inconsistently.
15791615 (indent-region (marker-position (plist-get sexp :beg-marker ))
1580- (marker-position (plist-get sexp :end-marker ))))))))
1616+ (marker-position (plist-get sexp :end-marker )))))
1617+ ; ; If `clojure-ts-align-separator' is used, `align-region' leaves trailing
1618+ ; ; whitespaces on empty lines.
1619+ (delete-trailing-whitespace beg (marker-position end)))))
1620+
1621+ (defun clojure-ts-indent-region (beg end )
1622+ " Like `indent-region' , but also maybe align forms.
15811623
1624+ Forms between BEG and END are aligned according to
1625+ `clojure-ts-align-forms-automatically' ."
1626+ (prog1 (let ((indent-region-function #'treesit-indent-region ))
1627+ (indent-region beg end))
1628+ (when clojure-ts-align-forms-automatically
1629+ (clojure-ts-align beg end))))
15821630
15831631(defvar clojure-ts-mode-map
15841632 (let ((map (make-sparse-keymap )))
@@ -1717,6 +1765,11 @@ REGEX-AVAILABLE."
17171765
17181766 (treesit-major-mode-setup)
17191767
1768+ ; ; We should assign this after calling `treesit-major-mode-setup' ,
1769+ ; ; otherwise it will be owerwritten.
1770+ (when clojure-ts-align-forms-automatically
1771+ (setq-local indent-region-function #'clojure-ts-indent-region ))
1772+
17201773 ; ; Initial indentation rules cache calculation.
17211774 (setq clojure-ts--semantic-indent-rules-cache
17221775 (clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules))
0 commit comments