@@ -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
@@ -1538,47 +1585,50 @@ between BEG and END."
1538
1585
(interactive (if (use-region-p )
1539
1586
(list (region-beginning ) (region-end ))
1540
1587
(save-excursion
1541
- (let ((start (clojure-ts--beginning-of-defun-pos))
1542
- (end (clojure-ts--end-of-defun-pos)))
1543
- (list start end)))))
1588
+ (if (not (treesit-defun-at-point))
1589
+ (user-error " No defun at point" )
1590
+ (let ((start (clojure-ts--beginning-of-defun-pos))
1591
+ (end (clojure-ts--end-of-defun-pos)))
1592
+ (list start end))))))
1544
1593
(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))))
1594
+ (let* ((sexps-to-align (clojure-ts--get-nodes-to-align beg (marker-position end)))
1595
+ ; ; We have to disable it here to avoid endless recursion.
1596
+ (clojure-ts-align-forms-automatically nil ))
1550
1597
(save-excursion
1551
- (indent-region beg ( marker-position end) )
1598
+ (indent-region beg end)
1552
1599
(dolist (sexp sexps-to-align)
1553
1600
; ; After reindenting a node, all other nodes in the `sexps-to-align'
1554
1601
; ; list become outdated, so we need to fetch updated nodes for every
1555
1602
; ; 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 ))
1603
+ (let* ((node (clojure-ts--node-from-sexp-data beg (marker-position end) sexp))
1567
1604
(sexp-type (plist-get sexp :sexp-type ))
1568
1605
(node-end (treesit-node-end node)))
1569
1606
(clojure-ts--point-to-align-position sexp-type node)
1570
1607
(align-region (point ) node-end nil
1571
1608
`((clojure-align (regexp . ,(lambda (&optional bound _noerror )
1572
- (clojure-ts--search-whitespace-after-next-sexp node bound)))
1609
+ (let ((updated-node (clojure-ts--node-from-sexp-data beg (marker-position end) sexp)))
1610
+ (clojure-ts--search-whitespace-after-next-sexp updated-node bound))))
1573
1611
(group . 1 )
1574
1612
(separate . , clojure-ts-align-separator )
1575
1613
(repeat . t )))
1576
1614
nil )
1577
1615
; ; After every iteration we have to re-indent the s-expression,
1578
1616
; ; otherwise some can be indented inconsistently.
1579
1617
(indent-region (marker-position (plist-get sexp :beg-marker ))
1580
- (marker-position (plist-get sexp :end-marker ))))))))
1618
+ (plist-get sexp :end-marker ))))
1619
+ ; ; If `clojure-ts-align-separator' is used, `align-region' leaves trailing
1620
+ ; ; whitespaces on empty lines.
1621
+ (delete-trailing-whitespace beg (marker-position end)))))
1622
+
1623
+ (defun clojure-ts-indent-region (beg end )
1624
+ " Like `indent-region' , but also maybe align forms.
1581
1625
1626
+ Forms between BEG and END are aligned according to
1627
+ `clojure-ts-align-forms-automatically' ."
1628
+ (prog1 (let ((indent-region-function #'treesit-indent-region ))
1629
+ (indent-region beg end))
1630
+ (when clojure-ts-align-forms-automatically
1631
+ (clojure-ts-align beg end))))
1582
1632
1583
1633
(defvar clojure-ts-mode-map
1584
1634
(let ((map (make-sparse-keymap )))
@@ -1717,6 +1767,11 @@ REGEX-AVAILABLE."
1717
1767
1718
1768
(treesit-major-mode-setup)
1719
1769
1770
+ ; ; We should assign this after calling `treesit-major-mode-setup' ,
1771
+ ; ; otherwise it will be owerwritten.
1772
+ (when clojure-ts-align-forms-automatically
1773
+ (setq-local indent-region-function #'clojure-ts-indent-region ))
1774
+
1720
1775
; ; Initial indentation rules cache calculation.
1721
1776
(setq clojure-ts--semantic-indent-rules-cache
1722
1777
(clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules))
0 commit comments