@@ -150,6 +150,16 @@ three or more semicolons will be treated as outline headings. If set to
150
150
:type 'string
151
151
:package-version '(clojure-ts-mode . " 0.4" ))
152
152
153
+ (defcustom clojure-ts-thread-all-but-last nil
154
+ " Non-nil means do not thread the last expression.
155
+
156
+ This means that `clojure-ts-thread-first-all' and
157
+ `clojure-ts-thread-last-all' not thread the deepest sexp inside the
158
+ current sexp."
159
+ :package-version '(clojure-ts-mode . " 0.4.0" )
160
+ :safe #'booleanp
161
+ :type 'boolean )
162
+
153
163
(defcustom clojure-ts-align-reader-conditionals nil
154
164
" Whether to align reader conditionals, as if they were maps."
155
165
:package-version '(clojure-ts-mode . " 0.4" )
@@ -1291,9 +1301,9 @@ according to the rule. If NODE is nil, use next node after BOL."
1291
1301
(clojure-ts--anon-fn-node-p parent))
1292
1302
; ; Can the following two clauses be replaced by checking indexes?
1293
1303
; ; Does the second child exist, and is it not equal to the current node?
1294
- (treesit- node-child parent 1 t )
1295
- (not (treesit-node-eq (treesit- node-child parent 1 t ) node))
1296
- (let ((first-child (treesit- node-child parent 0 t )))
1304
+ (clojure-ts-- node-child-skip-metadata parent 1 )
1305
+ (not (treesit-node-eq (clojure-ts-- node-child-skip-metadata parent 1 ) node))
1306
+ (let ((first-child (clojure-ts-- node-child-skip-metadata parent 0 )))
1297
1307
(or (clojure-ts--symbol-node-p first-child)
1298
1308
(clojure-ts--keyword-node-p first-child)
1299
1309
(clojure-ts--var-node-p first-child)))))
@@ -1381,17 +1391,11 @@ if NODE has metadata and its parent has type NODE-TYPE."
1381
1391
(treesit-node-type
1382
1392
(clojure-ts--node-with-metadata-parent node)))))
1383
1393
1384
- (defun clojure-ts--anchor-nth-sibling (n &optional named )
1385
- " Return the start of the Nth child of PARENT.
1386
-
1387
- NAMED non-nil means count only named nodes.
1388
-
1389
- NOTE: This is a replacement for built-in `nth-sibling' anchor preset,
1390
- which doesn't work properly for named nodes (see the bug
1391
- https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)"
1394
+ (defun clojure-ts--anchor-nth-sibling (n )
1395
+ " Return the start of the Nth child of PARENT skipping metadata."
1392
1396
(lambda (_n parent &rest _ )
1393
1397
(treesit-node-start
1394
- (treesit- node-child parent n named ))))
1398
+ (clojure-ts-- node-child-skip-metadata parent n))))
1395
1399
1396
1400
(defun clojure-ts--semantic-indent-rules ()
1397
1401
" Return a list of indentation rules for `treesit-simple-indent-rules' ."
@@ -1423,7 +1427,7 @@ https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)"
1423
1427
; ; https://guide.clojure.style/#threading-macros-alignment
1424
1428
(clojure-ts--match-threading-macro-arg prev-sibling 0 )
1425
1429
; ; https://guide.clojure.style/#vertically-align-fn-args
1426
- (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1 t ) 0 )
1430
+ (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1 ) 0 )
1427
1431
; ; https://guide.clojure.style/#one-space-indent
1428
1432
((parent-is " list_lit" ) parent 1 ))))
1429
1433
@@ -1539,8 +1543,8 @@ BOUND bounds the whitespace search."
1539
1543
(and (not (treesit-node-child-by-field-name cur-sexp " value" ))
1540
1544
(string-empty-p (clojure-ts--named-node-text cur-sexp))))
1541
1545
(treesit-end-of-thing 'sexp 2 'restricted )
1542
- (treesit-end-of-thing 'sexp 1 'restrict ))
1543
- (when (looking-at " ," )
1546
+ (treesit-end-of-thing 'sexp 1 'restricted ))
1547
+ (when (looking-at-p " ," )
1544
1548
(forward-char ))
1545
1549
; ; Move past any whitespace or comment.
1546
1550
(search-forward-regexp regex bound)
@@ -1744,7 +1748,7 @@ Forms between BEG and END are aligned according to
1744
1748
(goto-char first-child-start)
1745
1749
(treesit-beginning-of-thing 'sexp -1 )
1746
1750
(let ((contents (clojure-ts--delete-and-extract-sexp)))
1747
- (when (looking-at " *\n " )
1751
+ (when (looking-at-p " *\n " )
1748
1752
(join-line 'following ))
1749
1753
(just-one-space )
1750
1754
(goto-char first-child-start)
@@ -1753,9 +1757,11 @@ Forms between BEG and END are aligned according to
1753
1757
(clojure-ts--ensure-parens-around-function-name)
1754
1758
(down-list )
1755
1759
(forward-sexp )
1756
- (insert " " contents)
1757
- (when multiline-p
1758
- (insert " \n " )))))))
1760
+ (cond
1761
+ ((and multiline-p (looking-at-p " *\n " ))
1762
+ (insert " \n " contents))
1763
+ (multiline-p (insert " " contents " \n " ))
1764
+ (t (insert " " contents))))))))
1759
1765
1760
1766
(defun clojure-ts--unwind-thread-last ()
1761
1767
" Unwind a thread last macro once."
@@ -1768,7 +1774,7 @@ Forms between BEG and END are aligned according to
1768
1774
(goto-char first-child-start)
1769
1775
(treesit-beginning-of-thing 'sexp -1 )
1770
1776
(let ((contents (clojure-ts--delete-and-extract-sexp)))
1771
- (when (looking-at " *\n " )
1777
+ (when (looking-at-p " *\n " )
1772
1778
(join-line 'following ))
1773
1779
(just-one-space )
1774
1780
(goto-char first-child-start)
@@ -1804,10 +1810,16 @@ Forms between BEG and END are aligned according to
1804
1810
1805
1811
(defun clojure-ts--pop-out-of-threading ()
1806
1812
" Raise a sexp up a level to unwind a threading form."
1807
- (let ((threading-sexp (clojure-ts--threading-sexp-node)))
1813
+ (let* ((threading-sexp (clojure-ts--threading-sexp-node))
1814
+ (beg (thread-first threading-sexp
1815
+ (treesit-node-child 0 t )
1816
+ (treesit-node-start))))
1808
1817
(save-excursion
1809
1818
(clojure-ts--skip-first-child threading-sexp)
1810
- (raise-sexp ))))
1819
+ (delete-region beg (point ))
1820
+ ; ; `raise-sexp' doesn't work properly for function literals (it loses one
1821
+ ; ; of the parenthesis). Seems like an Emacs' bug.
1822
+ (delete-pair ))))
1811
1823
1812
1824
(defun clojure-ts--fix-sexp-whitespace ()
1813
1825
" Fix whitespace after unwinding a threading form."
@@ -1870,10 +1882,125 @@ With universal argument \\[universal-argument], fully unwinds thread."
1870
1882
(interactive )
1871
1883
(clojure-ts-unwind '(4 )))
1872
1884
1885
+ (defun clojure-ts--remove-superfluous-parens ()
1886
+ " Remove extra parens from a form."
1887
+ (when-let* ((node (treesit-thing-at-point 'sexp 'nested ))
1888
+ ((clojure-ts--list-node-p node))
1889
+ ((= 1 (treesit-node-child-count node t ))))
1890
+ (let ((delete-pair-blink-delay 0 ))
1891
+ (delete-pair ))))
1892
+
1893
+ (defun clojure-ts--thread-first ()
1894
+ " Thread a sexp using ->."
1895
+ (save-excursion
1896
+ (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node))
1897
+ (down-list )
1898
+ (treesit-beginning-of-thing 'sexp -1 )
1899
+ (let ((contents (clojure-ts--delete-and-extract-sexp)))
1900
+ (delete-char -1 )
1901
+ (when (looking-at-p " *\n " )
1902
+ (join-line 'following ))
1903
+ (backward-up-list )
1904
+ (insert contents " \n " )
1905
+ (clojure-ts--remove-superfluous-parens))))
1906
+
1907
+ (defun clojure-ts--thread-last ()
1908
+ " Thread a sexp using ->>."
1909
+ (save-excursion
1910
+ (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node))
1911
+ (treesit-end-of-thing 'sexp )
1912
+ (down-list -1 )
1913
+ (treesit-beginning-of-thing 'sexp )
1914
+ (let ((contents (clojure-ts--delete-and-extract-sexp)))
1915
+ (delete-char -1 )
1916
+ (treesit-end-of-thing 'sexp -1 'restricted )
1917
+ (when (looking-at-p " *\n " )
1918
+ (join-line 'following ))
1919
+ (backward-up-list )
1920
+ (insert contents " \n " )
1921
+ (clojure-ts--remove-superfluous-parens))))
1922
+
1923
+ (defun clojure-ts--threadable-p (node )
1924
+ " Return non-nil if expression NODE can be threaded.
1925
+
1926
+ First argument after threading symbol itself should be a list and it
1927
+ should have more than one named child."
1928
+ (let ((second-child (treesit-node-child node 1 t )))
1929
+ (and (clojure-ts--list-node-p second-child)
1930
+ (> (treesit-node-child-count second-child t ) 1 ))))
1931
+
1932
+ (defun clojure-ts-thread (&optional called-by-user-p )
1933
+ " Thread by one more level an existing threading macro.
1934
+
1935
+ If CALLED-BY-USER-P is non-nil (which is always TRUE when called
1936
+ interactively), the function signals a `user-error' if threading form
1937
+ cannot be found."
1938
+ (interactive " p" )
1939
+ (if-let* ((threading-sexp (clojure-ts--threading-sexp-node))
1940
+ ((clojure-ts--threadable-p threading-sexp))
1941
+ (sym (thread-first threading-sexp
1942
+ (treesit-node-child 0 t )
1943
+ (clojure-ts--named-node-text))))
1944
+ (let ((beg (thread-first threading-sexp
1945
+ (treesit-node-start)
1946
+ (copy-marker )))
1947
+ (end (thread-first threading-sexp
1948
+ (treesit-node-end)
1949
+ (copy-marker ))))
1950
+ (cond
1951
+ ((string-match-p (rx bol (* " some" ) " ->" eol) sym)
1952
+ (clojure-ts--thread-first))
1953
+ ((string-match-p (rx bol (* " some" ) " ->>" eol) sym)
1954
+ (clojure-ts--thread-last)))
1955
+ (indent-region beg end)
1956
+ (delete-trailing-whitespace beg end)
1957
+ t )
1958
+ (when called-by-user-p
1959
+ (user-error " No threading form at point" ))))
1960
+
1961
+ (defun clojure-ts--thread-all (first-or-last-thread but-last )
1962
+ " Fully thread the form at point.
1963
+
1964
+ FIRST-OR-LAST-THREAD is either \" ->\" or \" ->>\" .
1965
+
1966
+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1967
+ value is `clojure-ts-thread-all-but-last.' "
1968
+ (if-let* ((list-at-point (treesit-thing-at-point 'list 'nested )))
1969
+ (save-excursion
1970
+ (goto-char (treesit-node-start list-at-point))
1971
+ (insert-parentheses 1 )
1972
+ (insert first-or-last-thread)
1973
+ (while (clojure-ts-thread))
1974
+ (when (or but-last clojure-ts-thread-all-but-last)
1975
+ (clojure-ts-unwind)))
1976
+ (user-error " No list to thread at point" )))
1977
+
1978
+ (defun clojure-ts-thread-first-all (but-last )
1979
+ " Fully thread the form at point using ->.
1980
+
1981
+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1982
+ value is `clojure-ts-thread-all-but-last' ."
1983
+ (interactive " P" )
1984
+ (clojure-ts--thread-all " -> " but-last))
1985
+
1986
+ (defun clojure-ts-thread-last-all (but-last )
1987
+ " Fully thread the form at point using ->>.
1988
+
1989
+ When BUT-LAST is non-nil, the last expression is not threaded. Default
1990
+ value is `clojure-ts-thread-all-but-last' ."
1991
+ (interactive " P" )
1992
+ (clojure-ts--thread-all " ->> " but-last))
1993
+
1873
1994
(defvar clojure-ts-refactor-map
1874
1995
(let ((map (make-sparse-keymap )))
1996
+ (keymap-set map " C-t" #'clojure-ts-thread )
1997
+ (keymap-set map " t" #'clojure-ts-thread )
1875
1998
(keymap-set map " C-u" #'clojure-ts-unwind )
1876
1999
(keymap-set map " u" #'clojure-ts-unwind )
2000
+ (keymap-set map " C-f" #'clojure-ts-thread-first-all )
2001
+ (keymap-set map " f" #'clojure-ts-thread-first-all )
2002
+ (keymap-set map " C-l" #'clojure-ts-thread-last-all )
2003
+ (keymap-set map " l" #'clojure-ts-thread-last-all )
1877
2004
map)
1878
2005
" Keymap for `clojure-ts-mode' refactoring commands." )
1879
2006
@@ -1886,6 +2013,10 @@ With universal argument \\[universal-argument], fully unwinds thread."
1886
2013
'(" Clojure"
1887
2014
[" Align expression" clojure-ts-align]
1888
2015
(" Refactor -> and ->>"
2016
+ [" Thread once more" clojure-ts-thread]
2017
+ [" Fully thread a form with ->" clojure-ts-thread-first-all]
2018
+ [" Fully thread a form with ->>" clojure-ts-thread-last-all]
2019
+ " --"
1889
2020
[" Unwind once" clojure-ts-unwind]
1890
2021
[" Fully unwind a threading macro" clojure-ts-unwind-all])))
1891
2022
map)
0 commit comments