@@ -197,6 +197,22 @@ double quotes on the third column."
197
197
:safe #'listp
198
198
:type '(repeat string))
199
199
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
+
200
216
(defvar clojure-ts-mode-remappings
201
217
'((clojure-mode . clojure-ts-mode)
202
218
(clojurescript-mode . clojure-ts-clojurescript-mode)
@@ -1340,6 +1356,9 @@ if NODE has metadata and its parent has type NODE-TYPE."
1340
1356
((parent-is " vec_lit" ) parent 1 ) ; ; https://guide.clojure.style/#bindings-alignment
1341
1357
((parent-is " map_lit" ) parent 1 ) ; ; https://guide.clojure.style/#map-keys-alignment
1342
1358
((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 )
1343
1362
; ; https://guide.clojure.style/#body-indentation
1344
1363
(clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2 )
1345
1364
; ; https://guide.clojure.style/#threading-macros-alignment
@@ -1447,40 +1466,67 @@ Regular expression and syntax analysis code is borrowed from
1447
1466
1448
1467
BOUND bounds the whitespace search."
1449
1468
(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 ))))))
1469
1497
(when (and bound (> (point ) bound))
1470
1498
(goto-char bound))))
1471
1499
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 )
1473
1516
" Return a plist of nodes data for alignment.
1474
1517
1475
- The search is limited by BEG, END and REGION-NODE .
1518
+ The search is limited by BEG, END.
1476
1519
1477
1520
Possible node types are: map, bindings-vec, cond or read-cond.
1478
1521
1479
1522
The returned value is a list of property lists. Each property list
1480
1523
includes `:sexp-type' , `:node' , `:beg-marker' , and `:end-marker' .
1481
1524
Markers are necessary to fetch the same nodes after their boundaries
1482
1525
have 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
1484
1530
(append
1485
1531
`(((map_lit) @map)
1486
1532
((list_lit
@@ -1492,7 +1538,8 @@ have changed."
1492
1538
(:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym)))
1493
1539
@cond))
1494
1540
(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)))))))
1496
1543
(thread-last (treesit-query-capture region-node query beg end)
1497
1544
(seq-remove (lambda (elt ) (eq (car elt) 'sym )))
1498
1545
; ; When first node is reindented, all other nodes become
@@ -1542,43 +1589,44 @@ between BEG and END."
1542
1589
(end (clojure-ts--end-of-defun-pos)))
1543
1590
(list start end)))))
1544
1591
(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 ))
1550
1595
(save-excursion
1551
1596
(indent-region beg (marker-position end))
1552
1597
(dolist (sexp sexps-to-align)
1553
1598
; ; After reindenting a node, all other nodes in the `sexps-to-align'
1554
1599
; ; list become outdated, so we need to fetch updated nodes for every
1555
1600
; ; 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))
1567
1602
(sexp-type (plist-get sexp :sexp-type ))
1568
1603
(node-end (treesit-node-end node)))
1569
1604
(clojure-ts--point-to-align-position sexp-type node)
1570
- (align-region (point ) node-end nil
1605
+ (align-region (point ) node-end t
1571
1606
`((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))))
1573
1609
(group . 1 )
1574
1610
(separate . , clojure-ts-align-separator )
1575
1611
(repeat . t )))
1576
1612
nil )
1577
1613
; ; After every iteration we have to re-indent the s-expression,
1578
1614
; ; otherwise some can be indented inconsistently.
1579
1615
(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.
1581
1623
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))))
1582
1630
1583
1631
(defvar clojure-ts-mode-map
1584
1632
(let ((map (make-sparse-keymap )))
@@ -1717,6 +1765,11 @@ REGEX-AVAILABLE."
1717
1765
1718
1766
(treesit-major-mode-setup)
1719
1767
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
+
1720
1773
; ; Initial indentation rules cache calculation.
1721
1774
(setq clojure-ts--semantic-indent-rules-cache
1722
1775
(clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules))
0 commit comments